Running several web applications on a single server can quickly become a juggling act. You might have a Node.js API on port 3000, a Django site on port 8000, and a static React build served by a lightweight server on port 5000. Enter Nginx, the high‑performance web server that can sit in front of all those services and route traffic intelligently. In this guide we’ll walk you through configuring Nginx as a reverse proxy for multiple apps, complete with real commands, troubleshooting tips, and best‑practice recommendations.
What You’ll Need
- A Linux server (Ubuntu 22.04 LTS or similar) with sudo access
- Nginx installed (version 1.18+ recommended)
- At least two web applications already running on different local ports
- Domain names or subdomains pointing to your server’s IP (optional but recommended)
- A basic text editor (nano, vim, or VS Code)
Step 1: Install and Verify Nginx
First, make sure Nginx is installed and running. On Ubuntu you can use the apt package manager:
sudo apt update && sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl status nginx If the service is active, you should see a green “active (running)” line. Test the default page by visiting http://your_server_ip in a browser – you should see the classic Nginx welcome screen.
Step 2: Create Separate Server Blocks for Each App
Nginx uses server blocks (sometimes called virtual hosts) to decide how to handle incoming requests. For each app you’ll create a dedicated block inside /etc/nginx/sites-available and enable it with a symlink.
Assume we have two apps:
- app1.example.com – a Node.js API listening on
localhost:3000 - app2.example.com – a Django site listening on
localhost:8000
Start with app1:
sudo nano /etc/nginx/sites-available/app1.example.com Paste the following configuration (adjust the server_name and proxy_pass values as needed):
server {
listen 80;
server_name app1.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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Save and exit, then enable the block:
sudo ln -s /etc/nginx/sites-available/app1.example.com /etc/nginx/sites-enabled/ Repeat the process for app2.example.com, pointing to port 8000:
sudo nano /etc/nginx/sites-available/app2.example.com server {
listen 80;
server_name app2.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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/app2.example.com /etc/nginx/sites-enabled/ Step 3: Test Nginx Configuration and Reload
Before reloading, always validate the syntax. A tiny typo can bring down the whole web server.
sudo nginx -t If the test returns syntax is ok and test is successful, reload Nginx to apply the changes:
sudo systemctl reload nginx Now navigate to http://app1.example.com and http://app2.example.com. Each domain should proxy to its respective backend without exposing the internal ports.
Step 4: Secure the Proxy with Let’s Encrypt SSL
Plain HTTP is fine for internal testing, but production traffic should be encrypted. The easiest way is to use Certbot, which automates certificate issuance and Nginx configuration.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app1.example.com -d app2.example.com Follow the interactive prompts – choose to redirect all HTTP traffic to HTTPS when asked. Certbot will add listen 443 ssl; blocks and the necessary ssl_certificate directives automatically.
After the process completes, verify the SSL setup:
curl -I https://app1.example.com You should see a 200 OK response and the Strict-Transport-Security header if you accepted the default configuration.
Step 5: Fine‑Tune Proxy Settings for Performance and Security
Default proxy directives work, but you can squeeze extra performance and harden the connection. Add the following lines inside each location / block:
proxy_buffering on;
proxy_buffers 8 16k;
proxy_buffer_size 32k;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
These settings control how Nginx caches upstream responses and how long it waits for the backend. For APIs that return JSON, you might also want to enable compression:
gzip on;
gzip_types application/json text/css application/javascript;
Place the gzip directives in the main http context (usually /etc/nginx/nginx.conf) to affect all server blocks.
Step 6: Set Up Health Checks (Optional but Recommended)
If you plan to scale your apps or use multiple upstream instances, Nginx’s upstream module can perform basic health checks. Create a new file /etc/nginx/conf.d/upstreams.conf:
upstream app1_backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
# Add more servers here for load balancing
}
upstream app2_backend {
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
}
Then modify the server blocks to reference the upstream names:
proxy_pass http://app1_backend;
When a backend fails the configured number of times, Nginx temporarily removes it from rotation, preventing a cascade of 502 errors.
Common Mistakes to Avoid
1 Forgetting to set proxy_set_header Host $host: Backend apps often rely on the original Host header for redirects or virtual‑host logic. Omitting it can cause 404s or incorrect redirects.
2 Using localhost instead of 127.0.0.1 in proxy_pass: In some Docker‑based setups, localhost resolves inside the container, not the host, breaking the proxy.
3 Leaving default server block active: Nginx ships with a catch‑all block that can hijack unmatched domains. Delete or disable /etc/nginx/sites-enabled/default after you’ve added your own blocks.
4 Skipping nginx -t before reload: A single syntax error will bring down every site served by Nginx.
5 Not renewing SSL certificates: Let’s Encrypt certs expire after 90 days. Set up a cron job with certbot renew --quiet to automate renewal.
Tips and Tricks
Use variables for DRY configs. If several apps share the same security headers, define them once in include /etc/nginx/conf.d/security.conf; and reference the file in each block.
Leverage HTTP/2. Modern browsers benefit from multiplexed connections. Add listen 443 ssl http2; to your SSL server blocks.
Log per‑site. Create separate access logs for easier debugging: access_log /var/log/nginx/app1.access.log; inside each server block.
Test with curl. Use curl -I -H "Host: app1.example.com" http://127.0.0.1 to simulate a request without DNS changes.
Consider rate limiting. Prevent abuse by adding limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; and limit_req zone=one burst=20; inside the location block.
Frequently Asked Questions
Can I proxy WebSocket connections through Nginx?
Yes. The proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade"; directives in the Node.js example enable WebSocket support. Ensure your backend also listens for the Upgrade request.
What if my apps run on Docker containers?
Expose the container ports on the host (e.g., -p 3000:3000) and point Nginx to 127.0.0.1:3000. For a more isolated setup, create a dedicated Docker network and use the container name as the upstream host (e.g., proxy_pass http://node_app:3000;).
How do I troubleshoot a 502 Bad Gateway?
First, check the Nginx error log (/var/log/nginx/error.log) for clues. Common causes are:
- Backend service not running or listening on the wrong port.
- Firewall rules blocking local connections.
- Mismatched
proxy_passURL (missing trailing slash).
Confirm the backend works directly: curl http://127.0.0.1:3000. If it returns a proper response, the issue is likely in the Nginx configuration.
Conclusion
Configuring Nginx as a reverse proxy for multiple web applications is a powerful way to consolidate traffic, improve security, and lay the groundwork for scaling. By following the steps above—installing Nginx, creating dedicated server blocks, securing with SSL, and fine‑tuning performance—you’ll have a robust front‑end that can handle anything from simple static sites to complex microservice architectures. Remember to test each change, keep your certificates up to date, and monitor logs for early signs of trouble. Happy proxying!
Photo by Microsoft Copilot on Unsplash





