본문으로 건너뛰기 Node.js + Nginx Reverse Proxy Setup

Node.js + Nginx Reverse Proxy Setup

Node.js + Nginx Reverse Proxy Setup

이 글의 핵심

Running Node.js directly on port 80/443 in production is wrong. Nginx handles SSL termination, static files, compression, rate limiting, and routing ??so Node.js only handles application logic. This guide covers a complete production-ready Nginx + Node.js setup.

Architecture

Internet ??Nginx (port 80/443)
             —�?— /api/*     ??Node.js (port 3000)
             —�?— /static/*  ??Serve files directly (no Node.js)
             —�?— /ws/*      ??Node.js WebSocket (port 3000)

Nginx handles:

  • SSL termination (HTTPS ??HTTP to Node.js)
  • Gzip compression
  • Static file serving
  • Rate limiting
  • Security headers

1. Install Nginx

# Ubuntu/Debian
sudo apt update && sudo apt install nginx

# CentOS/RHEL
sudo dnf install nginx

# macOS
brew install nginx

# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx

# Check status
sudo systemctl status nginx

2. Basic Reverse Proxy

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://localhost:3000;

        # Required proxy headers
        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 site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/

# Test config
sudo nginx -t

# Reload (zero downtime)
sudo systemctl reload nginx

In Node.js, read the forwarded IP:

app.set('trust proxy', 1)  // trust first proxy (Nginx)

app.get('/', (req, res) => {
  console.log(req.ip)  // real client IP, not 127.0.0.1
  console.log(req.protocol)  // 'https' even if Node sees http
})

3. HTTPS with Let’s Encrypt

# Install Certbot
sudo apt install certbot python3-certbot-nginx

# Get certificate (Certbot configures Nginx automatically)
sudo certbot --nginx -d api.example.com -d www.example.com

# Test auto-renewal
sudo certbot renew --dry-run

# Auto-renewal is set up via systemd timer or cron automatically

After Certbot, your config becomes:

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;  # redirect HTTP ??HTTPS
}

server {
    listen 443 ssl;
    server_name api.example.com;

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

    # Modern SSL settings (added by Certbot)
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://localhost: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;
    }
}

4. Complete Production Config

# /etc/nginx/sites-available/myapp
upstream node_app {
    server localhost:3000;
    # Load balancing (see section 6)
    # server localhost:3001;
    # server localhost:3002;
    keepalive 32;
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # SSL
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY 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;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript
               application/xml+rss text/javascript image/svg+xml;

    # Buffer settings
    proxy_buffer_size          128k;
    proxy_buffers              4 256k;
    proxy_busy_buffers_size    256k;

    # Timeouts
    proxy_connect_timeout 60s;
    proxy_send_timeout    60s;
    proxy_read_timeout    60s;

    # Static files (served by Nginx, not Node.js)
    location /static/ {
        alias /var/www/myapp/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # API routes ??Node.js
    location /api/ {
        proxy_pass http://node_app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # for keepalive
        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;
    }

    # WebSocket
    location /ws/ {
        proxy_pass http://node_app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host       $host;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_read_timeout 86400s;  # keep WebSocket open
    }

    # Health check (no logs)
    location /health {
        proxy_pass http://node_app;
        access_log off;
    }

    # Block common attack patterns
    location ~* \.(env|git|svn) {
        deny all;
        return 404;
    }

    # Rate limiting (define zone in http block)
    limit_req zone=api burst=20 nodelay;
}
# /etc/nginx/nginx.conf ??http block additions
http {
    # Rate limit zones
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
    limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;

    # Connection limit
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    # Hide Nginx version
    server_tokens off;

    # Client body size
    client_max_body_size 10m;

    # Include site configs
    include /etc/nginx/sites-enabled/*;
}

5. WebSocket Proxying

location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;

    # WebSocket upgrade headers
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "upgrade";

    # Pass client info
    proxy_set_header Host       $host;
    proxy_set_header X-Real-IP  $remote_addr;

    # Don't close the connection
    proxy_read_timeout  86400s;
    proxy_send_timeout  86400s;
    proxy_connect_timeout 7s;
}

6. Load Balancing Multiple Node.js Instances

upstream node_cluster {
    least_conn;  # send to server with fewest connections (recommended)
    # round_robin (default)
    # ip_hash;  # sticky sessions ??same client ??same server

    server localhost:3000 weight=1;
    server localhost:3001 weight=1;
    server localhost:3002 weight=1;

    # Health check
    server localhost:3003 backup;  # only used when others are down

    keepalive 32;  # keep connections warm
}

server {
    listen 443 ssl;
    # ...

    location / {
        proxy_pass http://node_cluster;
        # ...
    }
}

Run multiple Node.js processes:

# PM2 cluster mode
pm2 start app.js -i max  # spawns one instance per CPU core
pm2 start app.js -i 4    # 4 instances on ports 3000-3003

7. Caching API Responses

# Define cache zone in http block
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
                 max_size=1g inactive=60m use_temp_path=off;

server {
    # ...

    location /api/public/ {
        proxy_pass http://node_app;

        # Cache responses
        proxy_cache api_cache;
        proxy_cache_valid 200 5m;     # cache 200 responses for 5 minutes
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
        proxy_cache_background_update on;
        proxy_cache_lock on;

        # Add cache status header for debugging
        add_header X-Cache-Status $upstream_cache_status;

        # Don't cache if Authorization header present
        proxy_cache_bypass $http_authorization;
        proxy_no_cache $http_authorization;
    }
}

Nginx Commands Reference

# Test config syntax
sudo nginx -t

# Reload config (zero downtime)
sudo systemctl reload nginx

# Restart (brief downtime)
sudo systemctl restart nginx

# View error logs
sudo tail -f /var/log/nginx/error.log

# View access logs
sudo tail -f /var/log/nginx/access.log

# View logs for specific site
sudo tail -f /var/log/nginx/myapp.access.log

# Check which ports Nginx is using
sudo ss -tlnp | grep nginx

Key Takeaways

ConcernNginx handlesNode.js handles
SSL/TLSTerminationNothing
Static filesDirect serveNothing
CompressionGzipNothing
Rate limitingPer-IP limitsPer-user limits
Load balancingUpstream poolNothing
Security headersHSTS, X-FrameApp-level
Application logicNothingEverything
  • Trust proxy: set app.set('trust proxy', 1) in Express to get real client IP
  • HTTP/2: use listen 443 ssl http2 ??multiplexed connections improve performance
  • WebSocket: requires Upgrade and Connection headers in location block
  • Let’s Encrypt: certbot --nginx configures SSL automatically, renews every 90 days

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Running Node.js directly on port 80/443 in production is wrong. Nginx handles SSL termination, static files, compression, rate limiting, and routing ??so Node.js only handles application logic. This guide covers a complete production-ready Nginx + Node.js setup.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Nginx, Node.js, DevOps, HTTPS, SSL, Backend.