راهاندازی Node.js روی VPS
با systemd، Nginx و HTTPS
اجرای آزمایشی <code>node server.js</code> برای توسعه کافی است، اما سرویس production باید پس از reboot بالا بیاید، با کاربر محدود اجرا شود، log قابلپیگیری داشته باشد و مستقیم روی اینترنت قرار نگیرد. این راهنما یک مسیر قابلنگهداری میسازد: نسخهی پشتیبانیشده Node.js، نصب تکرارپذیر وابستگیها، systemd برای چرخهی پردازش و Nginx برای ورودی HTTP/HTTPS.
۱) نسخهی پشتیبانیشده و روش نصب را آگاهانه انتخاب کنید
برای production از شاخهای استفاده کنید که در جدول رسمی Node.js وضعیت Active LTS یا Maintenance LTS دارد. شمارهی «جدیدترین نسخه» با زمان تغییر میکند؛ آن را در متن یک آموزش قدیمی حدس نزنید. نسخهی موردنیاز پروژه را از engines.node در package.json، lockfile، CI و مستندات dependencyها نیز تطبیق دهید.
صفحهی رسمی Download روشهای رسمی و community را تفکیک میکند. روی یک سرور production باید معلوم باشد binary از کجا آمده، چه کسی بهروزرسانی امنیتی آن را تأمین میکند و rollback نسخه چگونه انجام میشود. دستورهای ناشناس curl | bash را بدون دریافت، خواندن و اعتبارسنجی اجرا نکنید. اگر بستهی Ubuntu را بررسی میکنید، قبل از نصب نسخهی candidate را ببینید:
apt-cache policy nodejs npm
node --version
npm --version
command -v node
command -v npm
اگر Node هنوز نصب نیست، روش متناسب با policy خود را از صفحهی رسمی Download انتخاب کنید. version manager برای توسعه و چند نسخه راحت است، اما systemd محیط shell تعاملی را بارگذاری نمیکند؛ در unit باید مسیر مطلق binary را که command -v node نشان داده ثبت کنید. پس از نصب، نسخهها و منبع آنها را در مستند استقرار نگه دارید.
۲) برنامه را با کاربر اختصاصی و وابستگی تکرارپذیر آماده کنید
پردازش وب را با root اجرا نکنید. برای یک پروژهی تازه میتوانید کاربر سیستمی بدون ورود تعاملی و directory محدود بسازید. قبل از اجرای فرمانها مطمئن شوید نام appuser و مسیر /srv/myapp قبلاً متعلق به سرویس دیگری نیست:
getent passwd appuser
sudo adduser --system --group --home /srv/myapp appuser
sudo install -d -o appuser -g appuser -m 0750 /srv/myapp
کد را از فرایند deploy معتبر در این مسیر قرار دهید. فایلهای runtime باید برای appuser خواندنی و فقط مسیرهای لازم برای upload یا cache نوشتنی باشند؛ مجوز عمومی 777 راهحل نیست. اگر پروژه package-lock.json دارد، نصب production تکرارپذیر را از همان lockfile انجام دهید:
cd /srv/myapp
sudo -u appuser /ABSOLUTE/PATH/TO/npm ci
sudo -u appuser /ABSOLUTE/PATH/TO/npm test
sudo -u appuser /ABSOLUTE/PATH/TO/npm prune --omit=dev
مسیر مطلق npm را از command -v npm بردارید. npm ci پوشهی موجود node_modules را برای تطبیق با lockfile بازسازی میکند؛ آن را وسط نسخهی در حال سرویس اجرا نکنید. dependencyهای توسعه را برای test/build در release تازه نصب و پس از موفقیت از خروجی runtime کنار بگذارید. اگر پروژه build یا test script متفاوتی دارد، pipeline خود پروژه را جایگزین کنید.
۳) پورت، متغیرهای محیطی و endpoint سلامت را تعریف کنید
برنامهای که پشت Nginx است بهتر است فقط روی 127.0.0.1 گوش دهد. bind روی 0.0.0.0 پورت برنامه را روی تمام interfaceها قرار میدهد و ممکن است reverse proxy را دور بزند. پورت و host را از محیط بخوانید و مقدار production را در محیط systemd تعیین کنید:
NODE_ENV=production
HOST=127.0.0.1
PORT=3000
فایل محیطی نباید داخل Git باشد و باید فقط برای root و گروه سرویس قابلخواندن باشد. secret را در خود unit، command line، log یا صفحهی خطا چاپ نکنید. توجه کنید فایل EnvironmentFile دقیقاً shell script نیست؛ از نحو سادهی KEY=value و مستندات systemd استفاده کنید.
یک endpoint سبک مانند /health بسازید که برای سلامت process پاسخ دهد اما secret، stack trace، جزئیات دیتابیس یا اطلاعات کاربر را برنگرداند. پیش از systemd برنامه را با همان کاربر و محیط در loopback آزمایش و سپس process آزمایشی را متوقف کنید تا پورت اشغال نماند.
۴) یک unit فایل systemd با حداقل دسترسی بسازید
systemd پردازش را هنگام boot اجرا، وضعیت خروج را ثبت و در شکست غیرعادی دوباره تلاش میکند. مسیر ExecStart را با خروجی واقعی command -v node جایگزین کنید و نام فایل entry پروژه را بررسی کنید:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js application
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=appuser
Group=appuser
WorkingDirectory=/srv/myapp
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp.env
ExecStart=/ABSOLUTE/PATH/TO/node /srv/myapp/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
فایل را با sudoedit /etc/systemd/system/myapp.service بسازید. علامت منفی پیش از EnvironmentFile نبود فایل را نادیده میگیرد؛ اگر متغیرها برای راهاندازی ضروریاند، میتوانید آن را حذف کنید تا نبود config باعث شکست روشن شود. سپس unit را بارگذاری و کنترل کنید:
sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp --no-pager
sudo journalctl -u myapp -n 100 --no-pager
curl -i http://127.0.0.1:3000/health
Restart=on-failure جای رفع crash loop را نمیگیرد. اگر شمار restart بالا میرود، ابتدا stack trace، memory، dependency و config را اصلاح کنید. محدودسازیهای بیشتر systemd مثل filesystem protection مفیدند، اما باید با نیاز واقعی نوشتن فایل و child process برنامه آزموده شوند.
۵) Nginx، فایروال و HTTPS را جلوی برنامه قرار دهید
وقتی health محلی سالم است، در server block دامنه درخواستها را به loopback بدهید:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
sudo nginx -t
sudo systemctl reload nginx
curl -I -H 'Host: example.com' http://127.0.0.1
sudo ss -lntp | grep -E ':(80|443|3000)[[:space:]]'
انتظار میرود Nginx روی 80/443 عمومی و Node روی 127.0.0.1:3000 باشد. در UFW فقط SSH از مبدا مدیریتی و 80/443 برای وب را مجاز کنید؛ برای 3000 rule عمومی نسازید. پس از resolve شدن دامنه و آزمون HTTP، HTTPS را طبق راهنمای رسمی Certbot یا روش مدیریت گواهی سازمان خود فعال کنید.
اگر برنامه از WebSocket استفاده میکند، headerهای Upgrade و Connection را طبق مستندات Nginx اضافه کنید. اگر IP واقعی کاربر در برنامه مبنای امنیت یا rate limit است، فقط reverse proxy مورداعتماد را بپذیرید و تنظیم trust proxy فریمورک را دقیق محدود کنید؛ اعتماد کور به هر X-Forwarded-For قابل جعل است.
۶) انتشار نسخهی جدید را بدون ویرایش زنده انجام دهید
فایل نسخهی در حال اجرا را مستقیم ویرایش و dependencyهای آن را درجا بازسازی نکنید. برای هر انتشار یک directory تازه بسازید، کد و lockfile را قرار دهید، npm ci --omit=dev و آزمونها را با کاربر سرویس اجرا کنید و config لازم را بدون کپیکردن secret در release فراهم کنید. بعد از آزمون، جابهجایی نسخه را کوتاه و دارای rollback نگه دارید.
قبل از migration دیتابیس مشخص کنید تغییر backward-compatible است یا نه و بازگشت برنامه با schema جدید ممکن است یا خیر. migration مخرب را همزمان و بدون نسخهی پشتیبان و آزمون بازیابی اجرا نکنید. برای stop/restart، اثر روی درخواستهای فعال و jobهای پسزمینه را بشناسید.
sudo -u appuser npm outdated
sudo -u appuser npm audit
systemctl show myapp -p ActiveState -p SubState -p NRestarts
sudo journalctl -u myapp --since '10 minutes ago' --no-pager
npm audit ورودی تصمیم است، نه مجوز اجرای خودکار اصلاح major. خروجی را با مسیر exploit، نسخهی سازگار و changelog بسنجید. بعد از انتشار، health داخلی، پاسخ عمومی، خطاهای Nginx و log برنامه را با نسخهی قبل مقایسه کنید.
۷) خطاهای رایج و ترتیب تشخیص
- status=203/EXEC: مسیر ExecStart وجود ندارد یا قابل اجرا نیست؛ مسیر مطلق Node را کنترل کنید.
- status=200/CHDIR: WorkingDirectory اشتباه یا برای کاربر سرویس غیرقابل دسترسی است.
- EADDRINUSE: پردازش دیگری پورت را گرفته؛ با
ssمالک listener را پیدا کنید و process را کورکورانه kill نکنید. - 502 در Nginx: health محلی، وضعیت unit و پورت proxy_pass را بررسی کنید.
- restart loop: آخرین log، متغیرهای اجباری، دسترسی فایل و اتصال dependency را بررسی کنید.
- کارکرد در shell و شکست در systemd: PATH، working directory، user و environment دو محیط متفاوتاند.
systemctl status myapp --no-pager
sudo journalctl -u myapp -b --no-pager
sudo -u appuser test -r /srv/myapp/server.js
sudo ss -lntp | grep ':3000'
curl -i http://127.0.0.1:3000/health
sudo tail -n 100 /var/log/nginx/error.log
ترتیب را حفظ کنید: ابتدا process و health محلی، سپس Nginx محلی، بعد DNS/TLS و دسترسی بیرونی. این کار از تغییر بیدلیل firewall برای خطایی که در خود برنامه است جلوگیری میکند.
پرسشهای پرتکرار
برای production کدام نسخه Node.js را نصب کنم؟
شاخهای را انتخاب کنید که در جدول رسمی Node.js وضعیت Active LTS یا Maintenance LTS دارد و با engines پروژه و dependencyها سازگار است. شماره نسخه را در استقرار pin و زمان ارتقا را برنامهریزی کنید.
آیا اجرای node server.js در SSH کافی است؟
برای آزمون کوتاه بله، اما با قطع SSH یا reboot process از بین میرود. systemd وضعیت، boot، log و restart در شکست را بهشکل قابل مدیریت فراهم میکند.
چرا نباید برنامه Node.js را با root اجرا کنم؟
آسیبپذیری برنامه در حساب root میتواند کنترل کامل سیستم را بدهد. کاربر اختصاصی با دسترسی فقط به فایلها و پورتهای لازم دامنهی آسیب را کم میکند.
آیا پورت 3000 را باید در فایروال باز کنم؟
وقتی Nginx روی همان ماشین proxy است، خیر. Node را روی 127.0.0.1:3000 bind کنید و فقط پورتهای عمومی Nginx یعنی 80/443 را باز بگذارید.
چرا برنامه در ترمینال اجرا میشود اما در systemd نه؟
systemd shell تعاملی، nvm و متغیرهای profile شما را خودکار بارگذاری نمیکند. مسیر مطلق Node، WorkingDirectory، User، EnvironmentFile و مجوز فایلها را بررسی کنید.
برای بهروزرسانی dependencyها npm audit fix --force اجرا کنم؟
نه بهصورت کور. گزینه force ممکن است نسخههای major و تغییر ناسازگار وارد کند. گزارش را ارزیابی، changelog و تست را بررسی و نسخهی تازه را در release جدا آزمایش کنید.
منابع رسمی و مطالعه بیشتر
زیرساخت برنامهی Node.js را طراحی کنید
مصرف واقعی CPU، RAM، دیسک و وابستگیهای برنامه را اندازه بگیرید و سپس منابع مناسب را انتخاب کنید.