What Is a Reverse Proxy?
A reverse proxy is a server that sits between client devices and backend servers, forwarding client requests to the appropriate backend and returning the response to the client as if it came from the proxy itself. Unlike a forward proxy, which protects clients from the internet, a reverse proxy shields backend servers from direct client access, providing a single entry point for all incoming traffic.
Nginx is one of the most popular web servers and excels as a reverse proxy due to its event-driven architecture, low memory footprint, and extensive module ecosystem. Configuring Nginx as a reverse proxy allows you to route traffic based on hostnames, paths, or headers, terminate SSL/TLS, load balance across multiple backends, cache responses, and apply security policies—all with a lightweight, high-performance tool.
Why Use Nginx as a Reverse Proxy
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →- Load balancing: Distribute traffic across multiple backend servers to improve availability and performance.
- SSL termination: Offload TLS encryption and decryption from backend services to Nginx, simplifying certificate management.
- Security and isolation: Hide internal server details, block malicious requests, and enforce access controls before traffic reaches your application.
- Caching and compression: Serve static responses or compress data at the proxy layer, reducing latency and backend load.
- Single domain routing: Serve multiple applications under one domain by routing requests based on URI path or subdomain.
- WebSocket support: Seamlessly proxy WebSocket connections with the appropriate headers.
How to Configure Nginx as a Reverse Proxy
Prerequisites
You need a server with Nginx installed (version 1.9 or later is recommended for full features like WebSocket proxying). Basic familiarity with Nginx configuration files (/etc/nginx/nginx.conf and /etc/nginx/sites-available/) is assumed. Make sure you have reload permissions (e.g., via sudo).
Basic Reverse Proxy Setup
The core directive is proxy_pass, which tells Nginx where to forward requests. A minimal location block looks like this:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
}
}
This forwards all requests to example.com to a backend application running on localhost:3000. After saving the configuration, test it with nginx -t and reload Nginx with systemctl reload nginx (or nginx -s reload).
Passing Original Request Headers
By default, Nginx modifies some headers. To preserve the original client IP, protocol, and hostname, add proxy header directives:
location / {
proxy_pass http://backend_server;
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;
}
$host passes the original host header, X-Real-IP gives the client IP, X-Forwarded-For appends to a list of proxies, and X-Forwarded-Proto indicates http or https. Most frameworks rely on these headers for logging, redirects, and security checks.
Load Balancing Across Multiple Backends
Define an upstream block to group backend servers, then reference it in proxy_pass. Nginx supports several algorithms: round‑robin (default), least_conn, ip_hash, and random.
upstream app_cluster {
least_conn;
server 10.0.0.1:3000 weight=3;
server 10.0.0.2:3000;
server 10.0.0.3:3000 backup;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://app_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
The least_conn method sends requests to the server with the fewest active connections. weight adjusts proportion, and backup marks a server used only when others are down. You can also enable sticky sessions with the ip_hash directive.
SSL Termination and HTTPS Proxy
To serve traffic over HTTPS and forward to backend services (which can remain HTTP), set up a server block listening on port 443 with SSL certificates. Then proxy to the backend using http:// (or https:// if you want encrypted backend communication).
server {
listen 443 ssl http2;
server_name secure.example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://app_cluster;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name secure.example.com;
return 301 https://$host$request_uri;
}
The X-Forwarded-Proto https tells the backend the original request used HTTPS. The redirect block ensures all plain HTTP requests are upgraded.
WebSocket Proxying
WebSocket connections require the Upgrade and Connection headers. Nginx needs explicit configuration to handle the protocol switch:
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s;
}
Setting proxy_http_version 1.1 and the Upgrade/Connection headers allows Nginx to establish a persistent connection. A long proxy_read_timeout prevents idle WebSocket connections from being closed prematurely.
Response Caching
Nginx can cache backend responses to serve subsequent requests faster. Define a cache path and key, then enable caching inside a location block.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
...
location / {
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_pass http://app_cluster;
add_header X-Cache-Status $upstream_cache_status;
}
}
The proxy_cache_path directive defines storage and the shared memory zone. proxy_cache_valid sets TTLs per status code. proxy_cache_key builds a unique key from request parameters. The X-Cache-Status header helps debugging.
Security Headers and Basic Access Control
Use Nginx’s built‑in modules to add security headers and restrict access before requests reach the backend:
location / {
# Deny access to hidden files and certain user agents
if ($request_uri ~* "^/(\.well-known/)?(\.|\_)" ) {
return 403;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
proxy_pass http://app_cluster;
proxy_set_header ...;
}
You can also limit request methods, rate-limit with limit_req_zone, and block specific IPs using deny directives in the server context.
Best Practices for Nginx Reverse Proxy Configuration
-
Use a separate upstream block: Even for a single backend, defining an upstream makes it easier to add servers later and enables health checks with
max_failsandfail_timeout. -
Set timeouts carefully: Adjust
proxy_connect_timeout,proxy_read_timeout, andproxy_send_timeoutbased on application behavior. Long-running requests (like file uploads) need higher values. -
Always forward original request information: Include the standard
proxy_set_headerdirectives (Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto) to avoid misleading logs or broken redirects. -
Enable buffering for most use cases: The default
proxy_buffering on;improves throughput and protects backends from slow clients. Turn it off only for real‑time applications like WebSocket or Server‑Sent Events. -
Handle errors gracefully: Use
proxy_intercept_errors on;with customerror_pagedirectives to serve user‑friendly pages when backends fail, instead of exposing raw error responses. -
Test configuration before reloading: Always run
nginx -tto catch syntax errors. Usenginx -s reloadto apply changes without downtime. -
Monitor and rotate logs: Keep an eye on access and error logs. Use
logrotateor Nginx’s built‑in log rotation to prevent disk exhaustion. - Secure TLS configuration: Regularly update your cipher suites, enable HSTS, and consider using a tool like Mozilla’s SSL Configuration Generator.
-
Separate configurations: For multiple domains or services, split server blocks into separate files inside
/etc/nginx/sites-available/and symlink to/etc/nginx/sites-enabled/.
Conclusion
Configuring Nginx as a reverse proxy unlocks a powerful, efficient layer between your users and backend infrastructure. By mastering proxy_pass, header forwarding, load balancing, SSL termination, and caching, you can build a resilient and secure architecture. The examples above provide a solid foundation, but Nginx’s module system allows for even deeper customization—from dynamic routing with Lua to sophisticated rate limiting. Start with a simple proxy, gradually layer in improvements following the best practices, and you’ll have a production-grade setup that handles traffic with confidence.