Deploying an Express.js Website to a VPS with Nginx, PM2, and Ubuntu

This guide shows a production-friendly way to deploy an Express.js app on an Ubuntu VPS. You’ll run Node.js behind Nginx (reverse proxy) and keep it alive with PM2 (process manager), then optionally add HTTPS via Let’s Encrypt (Certbot). By the end, your app will survive reboots, serve a real domain, and be easy to update.

{"type":"doc","content":[{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"I. What you’re building"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Architecture overview"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"Express.js"},{"type":"text","text":" listens on a private port (e.g., "},{"type":"text","marks":[{"type":"code"}],"text":"127.0.0.1:3000"},{"type":"text","text":")"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"PM2"},{"type":"text","text":" runs and monitors the Node process, and can restore it on reboot "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://pm2.keymetrics.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"pm2.keymetrics.io"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://pm2.keymetrics.io/docs/usage/startup/?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"+1"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"Nginx"},{"type":"text","text":" listens on ports "},{"type":"text","marks":[{"type":"code"}],"text":"80/443"},{"type":"text","text":" and proxies requests to Express (and can handle WebSockets) "},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-as-a-reverse-proxy-on-ubuntu-22-04?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"DigitalOcean+1"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"Certbot"},{"type":"text","text":" can automatically configure HTTPS for Nginx and renew certificates "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://certbot.eff.org","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"certbot.eff.org"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://certbot.eff.org/instructions?os=ubuntufocal&ws=nginx&utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"+1"}]}]}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Prerequisites"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Ubuntu VPS (20.04/22.04/24.04)"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"A domain name pointing to your VPS IP (recommended for HTTPS)"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"SSH access with sudo privileges"}]}]}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"II. Prepare the Ubuntu VPS"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Update packages"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo apt update\nsudo apt -y upgrade\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Create a non-root deploy user (recommended)"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo adduser deploy\nsudo usermod -aG sudo deploy\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Log out and SSH back in as "},{"type":"text","marks":[{"type":"code"}],"text":"deploy"},{"type":"text","text":"."}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"3. Enable firewall (UFW)"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Allow SSH first:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo ufw allow OpenSSH\nsudo ufw enable\nsudo ufw status\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"III. Install Node.js and build tools"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Install Node.js"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"There are many valid approaches; a common production choice is NodeSource packages (or NVM if you prefer). NodeSource provides Debian/Ubuntu distributions and documentation pointers. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/nodesource/distributions?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"GitHub+1"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"If you’re using Ubuntu’s repo:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo apt -y install nodejs npm\nnode -v\nnpm -v\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Install build tools (often required for native deps)"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo apt -y install build-essential\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"IV. Upload your Express.js project"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Create the app directory"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo mkdir -p /var/www/my-express-app\nsudo chown -R $USER:$USER /var/www/my-express-app\ncd /var/www/my-express-app\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Deploy code (Git example)"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo apt -y install git\ngit clone <your-repo-url> .\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"3. Install dependencies"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"If you have a lockfile, prefer "},{"type":"text","marks":[{"type":"code"}],"text":"npm ci"},{"type":"text","text":":"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"npm ci\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Production install (skip dev dependencies):"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"npm ci --omit=dev\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"4. Ensure your app respects "},{"type":"text","marks":[{"type":"code"}],"text":"PORT"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Example server entry:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"const express = require(\"express\");\nconst app = express();\n\nconst port = process.env.PORT || 3000;\napp.get(\"/\", (req, res) => res.send(\"OK\"));\n\napp.listen(port, \"127.0.0.1\", () => {\n console.log(`Listening on http://127.0.0.1:${port}`);\n});\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"5. Quick local test on the VPS"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"NODE_ENV=production PORT=3000 node server.js\ncurl -I http://127.0.0.1:3000\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Stop the process after verifying."}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"V. Run Express.js with PM2"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"PM2 is a process manager that keeps your app online and provides an easy CLI workflow. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://pm2.keymetrics.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"pm2.keymetrics.io"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Install PM2 globally"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo npm install -g pm2@latest\npm2 -v\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"http://pm2.keymetrics.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"pm2.keymetrics.io"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Start the app with a stable name"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"If your "},{"type":"text","marks":[{"type":"code"}],"text":"package.json"},{"type":"text","text":" has "},{"type":"text","marks":[{"type":"code"}],"text":"\"start\": \"node server.js\""},{"type":"text","text":":"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"cd /var/www/my-express-app\npm2 start npm --name \"my-express-app\" -- start\npm2 status\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"3. Enable PM2 startup on reboot (systemd)"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"PM2 can generate startup scripts for systemd (Ubuntu uses systemd). "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://pm2.keymetrics.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"pm2.keymetrics.io"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://pm2.keymetrics.io/docs/usage/startup/?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"+1"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"pm2 startup\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"PM2 will print a command. Copy/paste it (it usually starts with "},{"type":"text","marks":[{"type":"code"}],"text":"sudo env ... pm2 startup systemd ..."},{"type":"text","text":")."}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Then persist the process list:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"pm2 save\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"http://pm2.keymetrics.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"pm2.keymetrics.io"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://pm2.keymetrics.io/docs/usage/startup/?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"+1"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"4. Handy PM2 operations"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"pm2 logs my-express-app\npm2 restart my-express-app\npm2 reload my-express-app\npm2 stop my-express-app\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"VI. Install and configure Nginx as a reverse proxy"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Install Nginx"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo apt -y install nginx\nsudo systemctl enable --now nginx\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Allow HTTP/HTTPS traffic:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo ufw allow \"Nginx Full\"\nsudo ufw status\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Create an Nginx server block"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Create:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo nano /etc/nginx/sites-available/my-express-app\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Example configuration:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"server {\n listen 80;\n server_name example.com www.example.com;\n\n # Optional: serve static files directly via Nginx\n # root /var/www/my-express-app/public;\n\n location / {\n proxy_pass http://127.0.0.1:3000;\n\n # Preserve host + client information\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n\n # WebSocket support\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n }\n}\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Nginx’s official documentation describes WebSocket proxying and the HTTP/1.1 upgrade mechanism. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://nginx.org","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"nginx.org"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Enable the site:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo ln -s /etc/nginx/sites-available/my-express-app /etc/nginx/sites-enabled/\nsudo nginx -t\nsudo systemctl reload nginx\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Test:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"curl -I http://example.com\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"VII. Add HTTPS with Let’s Encrypt (Certbot)"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Install Certbot and obtain a certificate"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"The EFF’s Certbot instructions for Nginx cover installation and usage on Ubuntu. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://certbot.eff.org","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"certbot.eff.org"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Run:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo certbot --nginx\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"DigitalOcean also notes that "},{"type":"text","marks":[{"type":"code"}],"text":"--nginx"},{"type":"text","text":" allows Certbot to analyze and modify Nginx server blocks to enable TLS and optional HTTP→HTTPS redirect. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04?utm_source=chatgpt.com","target":"_blank","rel":"noopener","class":"flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"}}],"text":"DigitalOcean"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Test auto-renewal"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"sudo certbot renew --dry-run\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"http://certbot.eff.org","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"certbot.eff.org"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"VIII. Deployment workflow (safe and repeatable)"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Typical update routine"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"cd /var/www/my-express-app\ngit pull\nnpm ci --omit=dev\npm2 reload my-express-app\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. Keep Node private"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Express should bind to "},{"type":"text","marks":[{"type":"code"}],"text":"127.0.0.1"},{"type":"text","text":" (not "},{"type":"text","marks":[{"type":"code"}],"text":"0.0.0.0"},{"type":"text","text":")"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Do "},{"type":"text","marks":[{"type":"bold"}],"text":"not"},{"type":"text","text":" open port "},{"type":"text","marks":[{"type":"code"}],"text":"3000"},{"type":"text","text":" in your firewall"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Only Nginx is public (ports 80/443)"}]}]}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"IX. Troubleshooting"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"1. Nginx shows “502 Bad Gateway”"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Common causes:"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"App isn’t running"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"App is listening on the wrong port/IP"}]}]},{"type":"listItem","content":[{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Nginx proxies to the wrong upstream"}]}]}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Check:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"pm2 status\npm2 logs my-express-app\nsudo tail -n 200 /var/log/nginx/error.log\n"}]},{"type":"heading","attrs":{"textAlign":null,"level":3},"content":[{"type":"text","text":"2. WebSockets ("},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://Socket.IO","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"Socket.IO"},{"type":"text","text":") don’t connect"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Make sure you included:"}]},{"type":"codeBlock","attrs":{"language":null},"content":[{"type":"text","text":"proxy_http_version 1.1;\nproxy_set_header Upgrade $http_upgrade;\nproxy_set_header Connection \"upgrade\";\n"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"http://Socket.IO","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"Socket.IO"},{"type":"text","text":"’s documentation includes reverse proxy configuration examples for Nginx. "},{"type":"text","marks":[{"type":"link","attrs":{"href":"http://socket.io","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"socket.io"}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"Conclusion"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","text":"Deploying Express.js on an Ubuntu VPS with Nginx and PM2 is a strong baseline for production: Nginx handles the public-facing layer (domain, TLS, WebSockets, buffering), while PM2 keeps your Node process stable and restores it after reboots. Once this is working, you can evolve into CI/CD, multi-instance scaling, or separating the database onto its own server."}]},{"type":"heading","attrs":{"textAlign":null,"level":2},"content":[{"type":"text","text":"References"}]},{"type":"paragraph","attrs":{"textAlign":null},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"https://pm2.keymetrics.io/docs/usage/quick-start/","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://pm2.keymetrics.io/docs/usage/quick-start/"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://pm2.keymetrics.io/docs/usage/startup/","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://pm2.keymetrics.io/docs/usage/startup/"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.digitalocean.com/community/tutorials/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://www.digitalocean.com/community/tutorials/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-as-a-reverse-proxy-on-ubuntu-22-04","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-as-a-reverse-proxy-on-ubuntu-22-04"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://nginx.org/en/docs/http/websocket.html","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://nginx.org/en/docs/http/websocket.html"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://socket.io/docs/v4/reverse-proxy/","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://socket.io/docs/v4/reverse-proxy/"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://certbot.eff.org/instructions?os=ubuntufocal&ws=nginx","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://certbot.eff.org/instructions?os=ubuntufocal&ws=nginx"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04"},{"type":"hardBreak"},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/nodesource/distributions","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"https://github.com/nodesource/distributions"}]}]}