A reverse proxy receives public traffic on ports 80 and 443 and forwards it to applications that listen only locally. It gives you HTTPS, several sites on one IP and a single place for logs and limits. This guide sets it up on a VPS Linux Infrawire for Node.js, Python or Docker apps.
📋 Prerequisites
- A Linux VPS (Ubuntu 24.04 / Debian 12 or AlmaLinux 9 / Rocky 9) with root SSH access. The IP and the root password are in your customer area.
- A domain
example.comwith A/AAAA records pointing toVPS_IP. - An application listening on a local port, for example
127.0.0.1:3000.
📦 Install Nginx
Bash# Debian / Ubuntu sudo apt update && sudo apt install -y nginx # AlmaLinux / Rocky sudo dnf install -y nginx sudo systemctl enable --now nginx
On AlmaLinux/Rocky, SELinux blocks Nginx from connecting to backends by default. Allow it once:
Bashsudo setsebool -P httpd_can_network_connect 1
🚀 A first proxied application
Your app must listen on 127.0.0.1, not on 0.0.0.0, so it is only reachable through Nginx. Whatever the language, the pattern is the same: node server.js on port 3000, gunicorn -b 127.0.0.1:8000 app:app for Python, or a container published with docker run -d -p 127.0.0.1:3000:80 IMAGE.
Create /etc/nginx/sites-available/example.com (on AlmaLinux/Rocky use /etc/nginx/conf.d/example.com.conf):
Nginx1server { 2 listen 80; 3 listen [::]:80; 4 server_name example.com; 5 6 location / { 7 proxy_pass http://127.0.0.1:3000; 8 proxy_http_version 1.1; 9 proxy_set_header Host $host; 10 proxy_set_header X-Real-IP $remote_addr; 11 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 proxy_set_header X-Forwarded-Proto $scheme; 13 } 14}
Enable it on Debian/Ubuntu, then validate and reload:
Bashsudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx
The X-Forwarded-* headers let the application know the real client IP and whether the visitor used HTTPS. Without them, your app only sees 127.0.0.1 and may generate http:// links. Many frameworks must also be told to trust the proxy (for example app.set('trust proxy', 1) in Express).
🔌 WebSocket support
WebSocket needs the HTTP upgrade headers. Add a map at the top of the http context (for example in /etc/nginx/conf.d/websocket.conf):
Nginxmap $http_upgrade $connection_upgrade { default upgrade; '' close; }
Then in the location block:
Nginxproxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade;
⏱️ Timeouts and body size
Defaults are 60 seconds and 1 MB. Adjust them for slow endpoints or uploads:
Nginxproxy_connect_timeout 5s; proxy_read_timeout 120s; proxy_send_timeout 120s; client_max_body_size 50m;
For a long-lived WebSocket, raise proxy_read_timeout (for example 3600s) or send pings from the application.
🔒 HTTPS with Certbot
Open the firewall for 80 and 443 first, then:
Bash# Debian / Ubuntu sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d example.com
On AlmaLinux/Rocky, install Certbot with sudo dnf install -y epel-release followed by sudo dnf install -y certbot python3-certbot-nginx. Certbot adds the certificate to your server block, offers the HTTP to HTTPS redirect and installs automatic renewal. Check it with:
Bashsudo certbot renew --dry-run
🗂️ Several apps on one VPS
Create one server block per domain, each with its own server_name and its own local port:
Nginx1server { 2 listen 80; 3 server_name app1.example.com; 4 location / { proxy_pass http://127.0.0.1:3000; } 5} 6 7server { 8 listen 80; 9 server_name app2.example.com; 10 location / { proxy_pass http://127.0.0.1:8000; } 11}
Repeat the proxy headers in each block, then run Certbot with -d app1.example.com -d app2.example.com. You can also split by path (location /api/ { proxy_pass http://127.0.0.1:8000/; }); the trailing slash then strips the prefix.
✅ Test
Bashcurl -I https://example.com ss -ltnp | grep -E ':(80|443|3000)' sudo tail -f /var/log/nginx/error.log
The backend port should be bound to 127.0.0.1 only, while Nginx listens on 80 and 443.
🛠️ Troubleshooting
502 Bad Gateway. The app is stopped or on another port. Check with ss -ltnp, then start it. On AlmaLinux/Rocky, a Permission denied in the error log means the SELinux boolean above is missing.
504 Gateway Timeout. The backend takes longer than proxy_read_timeout. Raise it or optimize the endpoint.
WebSocket disconnects or fails with 400/426. The Upgrade and Connection headers are missing, or proxy_http_version 1.1; is absent.
Wrong client IP in logs. X-Forwarded-For is sent but your app does not trust the proxy. Enable the trust setting of your framework.
🎉 Your applications are now served behind Nginx with HTTPS, clean headers and room for as many sites as your VPS can handle.