What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →From a fresh Linux server to a production Node.js app behind Nginx with HTTPS, a process manager, a firewall and automatic restarts. A complete checklist you can follow and reuse.
Managed platforms are great, but at some point you'll want, or need, to run an app on your own server. Maybe it's cost, control, WebSockets or a background worker. A virtual private server (VPS) gives you all of that, along with responsibility for keeping it secure.
Here's a reliable path from a fresh Ubuntu server to a production Node.js app. The same pattern works for most backend stacks.
Pick any VPS provider and a current Ubuntu LTS release. Add your SSH key during setup (SSH keys explained), then:
ssh root@YOUR_SERVER_IP
Don't run everything as root.
apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Log in as deploy in a new terminal to confirm it works, then disable root and password logins in /etc/ssh/sshd_config (PermitRootLogin no, PasswordAuthentication no) and reload SSH.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full' # after installing Nginx, or allow 80 and 443
sudo ufw enable
Your Node app itself will listen only on localhost; the world reaches it through Nginx.
Use a version manager or the official distribution packages for a current LTS version. For example, with nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.bashrc
nvm install --lts
(Read install scripts before piping them to a shell. That's a good habit everywhere.)
cd ~
git clone git@github.com:you/your-app.git app
cd app
npm ci --omit=dev
npm run build # if your app has a build step
Use a deploy key for private repositories.
Create a .env file on the server (never commit it) or set variables in your process manager config, then lock down its permissions:
chmod 600 .env
Your app needs to restart if it crashes and start on boot. Two good options:
PM2:
npm install -g pm2
pm2 start npm --name app -- start
pm2 save
pm2 startup # prints a command to enable boot startup; run it
Or a systemd service (/etc/systemd/system/app.service):
[Unit]
Description=My Node app
After=network.target
[Service]
User=deploy
WorkingDirectory=/home/deploy/app
EnvironmentFile=/home/deploy/app/.env
ExecStart=/home/deploy/.nvm/versions/node/vXX/bin/node server.js
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now app
Make sure the app listens on 127.0.0.1 and a port like 3000.
sudo apt install nginx
Create /etc/nginx/sites-available/app:
server {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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 ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Point your domain's DNS at the server (DNS guide). More on what Nginx is doing here: reverse proxies explained.
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
Certbot configures Nginx and sets up automatic renewal. (How HTTPS works.)
sudo apt install unattended-upgrades.pm2 logs or journalctl -u app -f. Rotate them so the disk doesn't fill. (Logging and monitoring.)A simple deploy script:
cd ~/app
git pull
npm ci --omit=dev
npm run build
pm2 reload app # or: sudo systemctl restart app
Run it over SSH, or from a GitHub Actions job once you're comfortable. (CI/CD starter.)
It's strongly recommended. Nginx handles HTTPS, compression, static files, request limits and buffering efficiently, and lets your app run without root privileges on an internal port.
Both work well. PM2 is Node-friendly with built-in log and cluster features; systemd is already on the server and manages any kind of process. Pick one and use it consistently.
Use pm2 reload with cluster mode, or run two instances behind Nginx and restart them one at a time, so one is always serving traffic.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →