How to set up Nginx as a reverse proxy with free SSL
Most real web applications should not be exposed directly to the internet on ports like 3000, 5000, or 8000. A cleaner production setup is to run the app on a private local port and place Nginx in front of it. Nginx receives public traffic, forwards requests to your app, and handles HTTPS, domain routing, headers, and basic operational polish.
Contents
1. Why use Nginx as a reverse proxy?
A reverse proxy sits between users and your application. Users connect to https://example.com, Nginx accepts the request on ports 80 and 443, and then Nginx passes that request to your application running locally, such as 127.0.0.1:3000.
This pattern is useful for Node.js, Django, Flask, FastAPI, Laravel, Rails, Go, and almost any HTTP service. It lets your app focus on application logic while Nginx handles the public edge.
- HTTPS is easier. Certbot can automatically configure Nginx and renew certificates.
- Your app stays private. The app can listen only on localhost instead of the public internet.
- Multiple apps can share one VPS. Use one domain per app and proxy each domain to a different local port.
- Deploys are cleaner. You can restart the app without changing DNS or public ports.
Need a VPS for your app?
Deploy a cloud server in minutes and pay with Bitcoin, Monero, or USDT. No KYC, no bank card.
Deploy a VPS2. What you need before starting
This guide assumes you have a fresh Ubuntu VPS and SSH access. You also need a domain or subdomain pointed to your server IP address with an A record.
In the examples below, replace these values with your own:
example.com # your domain www.example.com # optional www version 127.0.0.1:3000 # your app's local address and port
3. Install and check Nginx
Update the server and install Nginx:
sudo apt update sudo apt install nginx -y
Check that the service is running:
sudo systemctl status nginx
You can also visit your server IP in a browser:
http://YOUR_SERVER_IP
If Nginx installed correctly, you should see the default welcome page. That page is temporary; your custom domain config will replace it.
4. Open the firewall
If you use UFW, allow SSH first so you do not lock yourself out. Then allow HTTP and HTTPS:
sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status
Only open ports your server actually needs. For a normal web app behind Nginx, public access usually needs 22 for SSH, 80 for HTTP validation/redirects, and 443 for HTTPS.
5. Create the reverse proxy config
Create a new Nginx server block:
sudo nano /etc/nginx/sites-available/example.com
Add this configuration:
server {
listen 80;
server_name example.com www.example.com;
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;
}
}
Enable the site by linking it into sites-enabled:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
Test the Nginx syntax before reloading:
sudo nginx -t sudo systemctl reload nginx
Now visit http://example.com. If your app is running on 127.0.0.1:3000, Nginx should forward traffic to it.
What the proxy headers do
The proxy_set_header lines preserve useful request information. Your app can see the original host, the visitor's IP address, and whether the request came over HTTP or HTTPS. Many frameworks use these values for logs, redirects, and absolute URL generation.
6. Enable free SSL with Certbot
Certbot can request a free Let's Encrypt certificate and update your Nginx config automatically. Install it with Snap:
sudo snap install core sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot
Run Certbot for your domain:
sudo certbot --nginx -d example.com -d www.example.com
Follow the prompts. Certbot will verify the domain, install the certificate, and can configure HTTP-to-HTTPS redirects for you.
Test automatic renewal:
sudo certbot renew --dry-run
If the dry run succeeds, renewal is ready. Certbot installs a timer that periodically renews certificates before they expire.
7. Common errors and fixes
502 Bad Gateway
Nginx is reachable, but your app is not. Check whether the app is running and listening on the configured port:
sudo ss -tulpn | grep 3000 curl -I http://127.0.0.1:3000
If the app listens on a different port, update proxy_pass and reload Nginx.
Certbot cannot verify the domain
Check DNS and firewall rules. Your domain must point to this server, and port 80 must be reachable from the internet:
dig +short example.com sudo ufw status
Nginx reload fails
Run a syntax test and read the exact error line:
sudo nginx -t
The most common causes are a missing semicolon, duplicate server_name config, or a symlink pointing to the wrong file.
The app redirects to HTTP after SSL
Your framework may not trust reverse proxy headers by default. Make sure it reads X-Forwarded-Proto and knows it is behind a proxy. The exact setting depends on your framework.
8. Production checklist
- Domain A record points to the VPS IP
- App runs locally on a private port such as 127.0.0.1:3000
- Nginx config passes sudo nginx -t
- Firewall allows SSH, HTTP, and HTTPS
- Certbot certificate is installed and HTTPS loads correctly
- sudo certbot renew --dry-run succeeds
- App process is managed by systemd, Docker, PM2, Gunicorn, or another supervisor
- Logs and backups are in place before real users arrive
FAQ
Do I need Nginx if my app already has a built-in web server?
Can I host multiple apps on one VPS?
Why does Certbot need port 80?
What does 502 Bad Gateway mean?
Further reading
- Nginx reverse proxy documentation
- Certbot instructions for Nginx on Linux
- Ubuntu firewall documentation
GhostVPS is an anonymous, no-KYC VPS host on real DigitalOcean infrastructure. Pay with Bitcoin, Monero or USDT (TRC20); deploy in minutes from $6/mo. See pricing or open the panel.