How to Set Up a Reverse Proxy with Nginx for Self-Hosted Apps
Step-by-step guide to configuring Nginx as a reverse proxy for self-hosted applications like Grafana, Portainer, and Nextcloud — with SSL, load balancing, and security hardening.
What Is a Reverse Proxy and Why Use One?
A reverse proxy sits between the internet and your backend services. Instead of exposing each application on a different port (:3000, :8080, :9090), you route everything through a single entry point — port 443 with SSL.
Benefits:
- Single SSL certificate for all your apps
- Clean URLs —
grafana.example.cominstead of192.168.1.50:3000 - Security layer — hide backend IPs, add rate limiting, block bad bots
- Load balancing — distribute traffic across multiple servers
- Centralized logging — one place to monitor all HTTP traffic
Prerequisites
- A Linux server (Ubuntu 22.04/24.04 or Debian 12)
- A domain name pointing to your server's public IP
- One or more backend applications running on local ports
- Root or sudo access
Step 1: Install Nginx
Verify it's running:
Step 2: Basic Reverse Proxy Configuration
Let's say you have Grafana running on port 3000. Create a new site config:
Enable the site:
What Each Header Does
| Header | Purpose |
|--------|---------|
| Host | Passes the original domain to the backend |
| X-Real-IP | Sends the client's real IP address |
| X-Forwarded-For | Chain of all proxies the request passed through |
| X-Forwarded-Proto | Tells the backend if the original request was HTTP or HTTPS |
Step 3: Add SSL with Let's Encrypt
Install Certbot:
Generate and auto-configure SSL:
Certbot automatically:
- Obtains a free SSL certificate
- Modifies your Nginx config to use HTTPS
- Adds a redirect from HTTP to HTTPS
- Sets up auto-renewal via systemd timer
Verify auto-renewal:
Your config now looks like this (auto-modified by Certbot):
Step 4: Proxy Multiple Applications
Here's a complete setup proxying Grafana, Portainer, and Nextcloud on different subdomains:
Generate certificates for all domains at once:
Step 5: WebSocket Support
Apps like Grafana Live, Portainer, and chat applications use WebSockets. Add WebSocket upgrade headers:
Without these headers, WebSocket connections will fail with 400 Bad Request.
Step 6: Security Hardening
Add Security Headers
Rate Limiting
Prevent brute-force attacks on login pages:
Block Common Exploits
IP Whitelisting (for admin panels)
Step 7: Load Balancing
Distribute traffic across multiple backend servers:
Load Balancing Methods
| Method | Directive | Use Case |
|--------|-----------|----------|
| Round Robin | (default) | Equal-capacity servers |
| Least Connections | least_conn | Varying request durations |
| IP Hash | ip_hash | Session persistence needed |
| Weighted | weight=3 | Servers with different capacity |
Health Checks
Nginx automatically removes a server from the pool after 3 failed attempts and retries after 30 seconds.
Step 8: Performance Tuning
Enable Gzip Compression
Proxy Buffering
Static File Caching
Complete Production Config Template
Here's a battle-tested config you can copy and adapt:
Troubleshooting
502 Bad Gateway
The backend application isn't running or isn't listening on the expected port:
504 Gateway Timeout
The backend is too slow to respond. Increase timeouts:
SSL Certificate Issues
Permission Denied Errors
Nginx vs Alternatives
| Feature | Nginx | Caddy | Traefik | HAProxy | |---------|-------|-------|---------|---------| | Auto SSL | With Certbot | Built-in | Built-in | No | | Config style | File-based | Caddyfile | Labels/YAML | File-based | | Docker integration | Manual | Good | Excellent | Manual | | Performance | Excellent | Good | Good | Excellent | | Learning curve | Medium | Low | Medium | High | | Best for | General purpose | Simplicity | Docker/K8s | High traffic |
Nginx remains the most widely deployed reverse proxy — powering over 30% of all websites. Its flexibility, performance, and massive community make it the safe default choice.