Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

Linux & Mac & Bash Command Dictionary

  1. Home
  2. Linux & Mac & Bash Command Dictionary
  3. Nginx Reverse Proxy

Nginx Reverse Proxy

The Nginx reverse proxy receives requests from clients and forwards them to an application server running behind it (Node.js, Django, Rails, C#, etc.). Because the app's port does not need to be exposed to the outside world, security and flexibility improve. Use the proxy_pass directive to specify the forwarding destination, and proxy_set_header to pass client information to the app.

Syntax

# -----------------------------------------------
#  proxy_pass — basic reverse proxy settings
# -----------------------------------------------

# proxy_pass {destination URL};
#   → Forwards the request to the specified URL
#   → Used inside a location block
#   Example: proxy_pass http://127.0.0.1:3000;
#   Example: proxy_pass http://backend_pool;   # upstream name

# proxy_set_header Host {value};
#   → Sets the Host header sent to the backend
#   → Using $host passes the hostname the client sent as-is
#   Example: proxy_set_header Host $host;

# proxy_set_header X-Real-IP {value};
#   → Passes the client's real IP address to the app
#   → Required because the app would only see Nginx's IP when proxied
#   Example: proxy_set_header X-Real-IP $remote_addr;

# proxy_set_header X-Forwarded-For {value};
#   → Passes a comma-separated list of IP addresses of each proxy in the chain
#   → Appending to the existing X-Forwarded-For header is the common pattern
#   Example: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# -----------------------------------------------
#  upstream — defining a backend pool
# -----------------------------------------------

# upstream {pool name} {
#     server {host}:{port} [weight={weight}];
#     ...
# }
#   → Groups multiple backend servers for load balancing
#   → The pool name is referenced in proxy_pass
#   Example:
#   upstream app_backend {
#       server 127.0.0.1:3000;
#       server 127.0.0.1:3001 weight=2;
#   }

# -----------------------------------------------
#  WebSocket support (Upgrade / Connection headers)
# -----------------------------------------------

# Apps that use WebSocket (Socket.io, etc.) require
# forwarding the HTTP Upgrade header

# proxy_http_version 1.1;
#   → WebSocket requires HTTP/1.1

# proxy_set_header Upgrade $http_upgrade;
#   → Forwards the Upgrade header as-is

# proxy_set_header Connection "upgrade";
#   → Sets the Connection header to "upgrade"

Directive Reference

DirectiveDescription
proxy_passSpecifies the backend URL to forward requests to. You can use a direct address like http://127.0.0.1:3000, or a pool name defined with upstream.
proxy_set_header HostSets the Host header sent to the backend. Specifying $host passes the hostname the client sent as-is.
proxy_set_header X-Real-IPPasses the client's real IP address to the app via $remote_addr. Used for access logging and rate limiting.
proxy_set_header X-Forwarded-ForAccumulates a comma-separated list of proxy IP addresses in the chain. Using $proxy_add_x_forwarded_for appends to any existing header value.
proxy_set_header X-Forwarded-ProtoPasses the protocol used between the client and Nginx ($scheme: http or https) to the app. Used for HTTPS redirect detection.
proxy_http_versionSpecifies the HTTP version to use with the backend. 1.1 is required for WebSocket support.
proxy_read_timeoutSpecifies the maximum time (in seconds) to wait for a response from the backend. The default is 60 seconds. Increase this for apps that perform long-running operations.
proxy_connect_timeoutSpecifies the maximum time (in seconds) to wait for a connection to the backend to be established. The default is 60 seconds.
proxy_bufferingControls whether responses from the backend are buffered. Setting it to off is effective for streaming and Server-Sent Events.
upstreamDefines a pool of backend servers. Listing multiple servers with the server directive enables round-robin load balancing.
proxy_cache_bypassBypasses the cache under specified conditions. Used to exclude dynamic content from caching.

Examples

/etc/nginx/sites-available/kyo-node.conf
# -----------------------------------------------
#  Reverse proxy configuration for a Node.js app
#  (port 3000) with WebSocket (Socket.io) support
# -----------------------------------------------

server {
    listen 80;
    server_name kyo-app.example.com;

    # HTTP → HTTPS redirect
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name kyo-app.example.com;

    ssl_certificate /etc/letsencrypt/live/kyo-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/kyo-app.example.com/privkey.pem;

    location / {
        # Forward to the Node.js app running on local port 3000
        proxy_pass http://127.0.0.1:3000;

        # Pass client information to the app
        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 HTTP/1.1 and the Upgrade header for WebSocket (Socket.io) support
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Extend the backend response timeout to 120 seconds
        proxy_read_timeout 120s;
    }
}

Run the following command:

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
     Active: active (running) since Wed 2026-03-25 09:00:00 JST
/etc/nginx/sites-available/iori-django.conf
# -----------------------------------------------
#  Reverse proxy configuration for a Django app
#  (Gunicorn / port 8000)
#  Static files (/static/) are served directly by Nginx
# -----------------------------------------------

upstream iori_gunicorn {
    # Gunicorn is listening on 127.0.0.1:8000
    server 127.0.0.1:8000;
}

server {
    listen 80;
    server_name iori-app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name iori-app.example.com;

    ssl_certificate /etc/letsencrypt/live/iori-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/iori-app.example.com/privkey.pem;

    # Nginx serves static files collected by Django's collectstatic directly,
    # bypassing the app server for better performance
    location /static/ {
        alias /var/www/iori-app/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    # Media files (user-uploaded images, etc.) are also served directly
    location /media/ {
        alias /var/www/iori-app/media/;
        expires 7d;
    }

    # All other requests are forwarded to Gunicorn
    location / {
        proxy_pass http://iori_gunicorn;

        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;

        # Set the maximum allowed file upload size to 20MB
        client_max_body_size 20M;

        # Extend the timeout for long-running operations (e.g., PDF generation)
        proxy_read_timeout 180s;
    }
}
/etc/nginx/sites-available/terry-rails.conf
# -----------------------------------------------
#  Reverse proxy configuration for a Rails app
#  (Puma / port 3000)
# -----------------------------------------------

upstream terry_puma {
    # When Puma is listening on a Unix domain socket
    server unix:/var/www/terry-app/tmp/puma.sock;
    # To listen on a TCP port instead, use the following:
    # server 127.0.0.1:3000;
}

server {
    listen 443 ssl;
    server_name terry-app.example.com;

    ssl_certificate /etc/letsencrypt/live/terry-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/terry-app.example.com/privkey.pem;

    # Serve Rails' public/ directory directly
    root /var/www/terry-app/public;

    # Serve the file directly if it exists in public/;
    # otherwise forward to Puma (standard Rack app pattern)
    try_files $uri/index.html $uri @terry_puma;

    location @terry_puma {
        proxy_pass http://terry_puma;

        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;
    }

    # Action Cable (WebSocket) endpoint
    location /cable {
        proxy_pass http://terry_puma;

        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host       $host;
    }
}
/etc/nginx/sites-available/mai-dotnet.conf
# -----------------------------------------------
#  Reverse proxy configuration for a C# / .NET app
#  (Kestrel / port 5000)
#  Used together with the ForwardedHeaders middleware
# -----------------------------------------------

server {
    listen 443 ssl;
    server_name mai-app.example.com;

    ssl_certificate /etc/letsencrypt/live/mai-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mai-app.example.com/privkey.pem;

    location / {
        # Kestrel is listening on port 5000
        proxy_pass http://127.0.0.1:5000;

        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 .NET ForwardedHeaders middleware reads X-Forwarded-Proto
        # to determine whether the connection is over HTTPS
        proxy_set_header X-Forwarded-Proto $scheme;

        # SignalR (WebSocket) support
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
Load balancing with upstream (Andy's API)
# -----------------------------------------------
#  Load balancing across two API servers
#  The second server receives twice as many requests via weight
# -----------------------------------------------

upstream andy_api {
    server 192.168.1.10:8080 weight=1;
    server 192.168.1.11:8080 weight=2;

    # Health check (Nginx Plus only)
    # keepalive 16;
}

server {
    listen 443 ssl;
    server_name api.andy-app.example.com;

    ssl_certificate /etc/letsencrypt/live/api.andy-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.andy-app.example.com/privkey.pem;

    location /api/ {
        proxy_pass http://andy_api;

        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;

        # Disabling buffering reduces latency for dynamic API responses
        proxy_buffering off;
    }
}

Run the following command:

$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ curl -I https://api.andy-app.example.com/api/health
HTTP/2 200
server: nginx/1.24.0 (Ubuntu)
content-type: application/json
x-powered-by: Express

Overview

A reverse proxy places Nginx as the public endpoint and hides app servers such as Node.js, Django, Rails, and C# from the outside world. Because proxy_set_header X-Real-IP and X-Forwarded-For pass the client's real IP to the app, access logging, rate limiting, and security checks all work correctly. Using a Unix domain socket (unix:/run/app.sock) gives slightly lower latency than a TCP socket, and is a common choice with Puma for Rails and Gunicorn for Python. For apps that use WebSocket (Socket.io, Action Cable, SignalR, etc.), setting proxy_http_version 1.1 along with the Upgrade and Connection headers is required. For an overview of Nginx itself and the basic structure of nginx.conf, see Nginx Configuration. For the overall deployment procedure for Django apps, see Deploying a Django App. For deploying Node.js / Express apps (PM2 + Nginx), see Deploying a Node.js / Express App.

If you find any errors or copyright issues, please .