Nginx Reverse Proxy and Deployment
Introduction
Nginx terminates HTTP on ports 80/443 and forwards traffic to your Java or Node process on localhost. It also serves static frontends efficiently. This chapter installs Nginx, configures proxy_pass, validates config, and walks a practical deploy checklist for a small VPS.
Prerequisites
- Networking basics
- Processes and systemd
- An app listening on
127.0.0.1:8080or:3000(or use examples as templates)
Install and Start Nginx
sudo apt update
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
curl -I http://127.0.0.1/Default site root: /var/www/html/. Place a test page:
echo '<h1>hello_code</h1>' | sudo tee /var/www/html/index.html
curl http://127.0.0.1/Config layout on Debian/Ubuntu:
| Path | Role |
|---|---|
/etc/nginx/nginx.conf | Main file |
/etc/nginx/sites-available/ | Site definitions |
/etc/nginx/sites-enabled/ | Symlinks to enabled sites |
/var/log/nginx/ | Access and error logs |
Static Site Only
/etc/nginx/sites-available/mysite:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/mysite;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Enable:
sudo mkdir -p /var/www/mysite
sudo cp -r ./dist/* /var/www/mysite/
sudo ln -sf /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxReverse Proxy to Spring Boot (8080)
Reload after edit:
sudo nginx -t && sudo systemctl reload nginxTip
Bind App to Localhost
Run Java/Node on 127.0.0.1:8080 so the port is not exposed publicly without Nginx—defense in depth with firewall.
Reverse Proxy to Node (3000)
WebSocket-friendly headers matter for some Next.js or Socket setups.
TLS (Awareness)
Production needs HTTPS—typically Certbot with Let’s Encrypt:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.comCertbot edits Nginx for you. Renewals run via systemd timer.
Deploy Checklist
Use this before and after each release:
Before deploy
-
df -h— enough disk (disk chapter) - Backup DB or snapshot if required
- Tag release in Git
Deploy steps
- Upload artifact:
scp/rsync/ CI (networking) -
sudo systemctl restart myapporpm2 restart my-api -
sudo nginx -t && sudo systemctl reload nginxif config changed
Verify
-
sudo ss -tlnp | grep -E ':80|:8080|:3000' -
curl -sf http://127.0.0.1:8080/actuator/health(adjust path) -
curl -I http://your-domain/through public DNS -
sudo ufw statusor cloud security group allows 80/443
Logs
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
sudo journalctl -u myapp -fRollback
- Previous JAR/archive kept in
/opt/myapp/releases/ -
systemctl restartwith old binary if health check fails
Mini Example: Proxy + Health Script
# App health on backend
curl -sf http://127.0.0.1:8080/health
# Through Nginx (after config)
curl -sf http://127.0.0.1/healthAutomate with shell scripts post-deploy.
FAQ
502 Bad Gateway?
Backend down, wrong proxy_pass port, or app bound only to a different interface—check ss -tlnp and app logs.
413 Request Entity Too Large?
Increase client_max_body_size in Nginx for big uploads.
Changes not visible?
Browser cache vs reload nginx—also confirm you edited sites-enabled file.
Nginx vs Apache?
Both work; Nginx is common for static + reverse proxy on small VPS setups covered here.