Bài 11: Nginx với Application Stack trong NGINX

Bài học về tích hợp Nginx với Application Stack - PHP-FPM configuration cho WordPress/Laravel, Nginx + Node.js với PM2, Python applications (uWSGI/Gunicorn), Ruby on Rails với Puma, Docker containers, WebSocket proxying và gRPC.

13 min read
Bài 11: Nginx với Application Stack trong NGINX

1. PHP-FPM Configuration

PHP-FPM (FastCGI Process Manager) là cách tốt nhất để chạy PHP với Nginx.

1.1. Install PHP-FPM

Ubuntu/Debian:

# Update packages
sudo apt update

# Install PHP-FPM và extensions
sudo apt install php8.1-fpm php8.1-mysql php8.1-mbstring php8.1-xml php8.1-curl php8.1-zip php8.1-gd

# Check service
sudo systemctl status php8.1-fpm

# Socket location: /var/run/php/php8.1-fpm.sock

CentOS/RHEL:

# Install EPEL and Remi repository
sudo yum install epel-release
sudo yum install https://rpms.remirepo.net/enterprise/remi-release-8.rpm

# Enable PHP 8.1
sudo yum module reset php
sudo yum module enable php:remi-8.1

# Install PHP-FPM
sudo yum install php php-fpm php-mysqlnd php-mbstring php-xml

# Start service
sudo systemctl start php-fpm
sudo systemctl enable php-fpm

1.2. Basic Nginx + PHP-FPM Configuration

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php index.html;
    
    # Logging
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
    
    # PHP-FPM configuration
    location ~ \.php$ {
        # Security: Check if file exists
        try_files $uri =404;
        
        # FastCGI parameters
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        
        # Include standard FastCGI params
        include fastcgi_params;
        
        # Additional params
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_param REQUEST_METHOD $request_method;
        fastcgi_param CONTENT_TYPE $content_type;
        fastcgi_param CONTENT_LENGTH $content_length;
        
        # Timeouts
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 180s;
        fastcgi_read_timeout 180s;
        
        # Buffering
        fastcgi_buffer_size 128k;
        fastcgi_buffers 256 16k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;
    }
    
    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # Static files
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

1.3. WordPress Configuration

server {
    listen 80;
    listen [::]:80;
    server_name blog.example.com;
    root /var/www/wordpress;
    index index.php;
    
    # Logging
    access_log /var/log/nginx/wordpress.access.log;
    error_log /var/log/nginx/wordpress.error.log;
    
    # Max upload size
    client_max_body_size 64M;
    
    # WordPress permalinks
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    # PHP processing
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        
        # WordPress-specific
        fastcgi_intercept_errors on;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        
        fastcgi_read_timeout 300s;
    }
    
    # Deny access to sensitive files
    location ~ /\.(ht|git|svn) {
        deny all;
    }
    
    location = /wp-config.php {
        deny all;
    }
    
    location = /xmlrpc.php {
        deny all;
    }
    
    # Cache static files
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # WordPress uploads
    location ~* ^/wp-content/uploads/.*\.(php|php3|php4|php5|phtml)$ {
        deny all;
    }
}

1.4. Laravel Configuration

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;
    root /var/www/laravel/public;
    index index.php;
    
    # Logging
    access_log /var/log/nginx/laravel.access.log;
    error_log /var/log/nginx/laravel.error.log;
    
    # Max upload size
    client_max_body_size 100M;
    
    # Add security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # Laravel routing
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    # PHP processing
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        
        # Laravel optimizations
        fastcgi_buffer_size 32k;
        fastcgi_buffers 8 16k;
        
        fastcgi_read_timeout 300s;
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 180s;
        
        # Hide PHP version
        fastcgi_hide_header X-Powered-By;
    }
    
    # Deny access to sensitive files
    location ~ /\.(?!well-known).* {
        deny all;
    }
    
    location ~ /\.env {
        deny all;
    }
    
    # Disable execution in storage
    location ~* ^/storage/.*\.(php|php3|php4|php5|phtml)$ {
        deny all;
    }
    
    # Cache static assets
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

1.5. PHP-FPM Pool Configuration

# Edit PHP-FPM pool config
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = nginx
group = nginx

# Socket or TCP
listen = /var/run/php/php8.1-fpm.sock
; listen = 127.0.0.1:9000

# Socket permissions
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

# Process manager
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

# Status page
pm.status_path = /status

# Ping page
ping.path = /ping

# Process priority
; process.priority = -19

# Timeouts
request_terminate_timeout = 300s
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/slow.log

# Environment variables
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

# PHP ini settings
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/sessions
php_value[upload_max_filesize] = 100M
php_value[post_max_size] = 100M
php_value[memory_limit] = 256M
php_value[max_execution_time] = 300

Restart PHP-FPM:

sudo systemctl restart php8.1-fpm

1.6. Multiple PHP Versions

# Install multiple PHP versions
sudo apt install php7.4-fpm php8.0-fpm php8.1-fpm php8.2-fpm

# Configure different sites with different PHP versions

# Site 1 - PHP 7.4
server {
    listen 80;
    server_name legacy.example.com;
    root /var/www/legacy;
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

# Site 2 - PHP 8.1
server {
    listen 80;
    server_name app.example.com;
    root /var/www/app;
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

# Site 3 - PHP 8.2
server {
    listen 80;
    server_name new.example.com;
    root /var/www/new;
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

2. Nginx + Node.js

2.1. Basic Node.js Application

Create simple Node.js app:

// app.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.json({ message: 'Hello from Node.js!' });
});

app.get('/api/users', (req, res) => {
    res.json([
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' }
    ]);
});

app.listen(port, '127.0.0.1', () => {
    console.log(`Server running on port ${port}`);
});

Install dependencies:

npm init -y
npm install express

Run application:

node app.js

2.2. Nginx Reverse Proxy for Node.js

upstream nodejs_backend {
    # Single instance
    server 127.0.0.1:3000;
    
    # Or multiple instances (PM2 cluster mode)
    # server 127.0.0.1:3000;
    # server 127.0.0.1:3001;
    # server 127.0.0.1:3002;
    # server 127.0.0.1:3003;
    
    keepalive 64;
}

server {
    listen 80;
    listen [::]:80;
    server_name api.example.com;
    
    # Logging
    access_log /var/log/nginx/nodejs.access.log;
    error_log /var/log/nginx/nodejs.error.log;
    
    # 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;
    
    # Proxy to Node.js
    location / {
        proxy_pass http://nodejs_backend;
        
        # HTTP version and connection
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        # Headers
        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_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }
    
    # Health check endpoint
    location /health {
        proxy_pass http://nodejs_backend/health;
        access_log off;
    }
    
    # Static files (if served by Node.js)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        proxy_pass http://nodejs_backend;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

2.3. PM2 Process Manager

Install PM2:

npm install -g pm2

Start application with PM2:

# Start single instance
pm2 start app.js --name "api"

# Start with cluster mode (multiple instances)
pm2 start app.js -i max --name "api"

# Or use ecosystem file
pm2 start ecosystem.config.js

Ecosystem configuration:

// ecosystem.config.js
module.exports = {
    apps: [{
        name: 'api',
        script: './app.js',
        instances: 4,
        exec_mode: 'cluster',
        env: {
            NODE_ENV: 'production',
            PORT: 3000
        },
        max_memory_restart: '500M',
        error_file: './logs/err.log',
        out_file: './logs/out.log',
        log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
        merge_logs: true
    }]
};

Nginx với PM2 cluster:

upstream nodejs_cluster {
    least_conn;
    
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
    
    keepalive 64;
}

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://nodejs_cluster;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

PM2 commands:

# List processes
pm2 list

# Monitor
pm2 monit

# Logs
pm2 logs

# Restart
pm2 restart api

# Stop
pm2 stop api

# Delete
pm2 delete api

# Save configuration
pm2 save

# Startup script
pm2 startup

2.4. Next.js Application

upstream nextjs {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name app.example.com;
    
    # Logging
    access_log /var/log/nginx/nextjs.access.log;
    error_log /var/log/nginx/nextjs.error.log;
    
    # Gzip compression
    gzip on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
    
    location / {
        proxy_pass http://nextjs;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        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;
    }
    
    # Next.js static files
    location /_next/static/ {
        proxy_pass http://nextjs;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Next.js images
    location /_next/image {
        proxy_pass http://nextjs;
        proxy_cache_valid 200 1h;
    }
}

3. Python Applications (uWSGI/Gunicorn)

3.1. Flask Application

Create Flask app:

# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return jsonify({'message': 'Hello from Flask!'})

@app.route('/api/users')
def users():
    return jsonify([
        {'id': 1, 'name': 'Alice'},
        {'id': 2, 'name': 'Bob'}
    ])

if __name__ == '__main__':
    app.run(debug=False)

Install dependencies:

pip install flask gunicorn

3.2. Gunicorn Configuration

Run with Gunicorn:

# Basic
gunicorn app:app

# With options
gunicorn --bind 127.0.0.1:8000 --workers 4 --timeout 60 app:app

# With config file
gunicorn -c gunicorn_config.py app:app

Gunicorn config file:

# gunicorn_config.py
import multiprocessing

# Server socket
bind = '127.0.0.1:8000'
backlog = 2048

# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
timeout = 60
keepalive = 2

# Logging
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'info'

# Process naming
proc_name = 'flask_app'

# Server mechanics
daemon = False
pidfile = '/var/run/gunicorn.pid'
umask = 0
user = None
group = None
tmp_upload_dir = None

# SSL
keyfile = None
certfile = None

3.3. Nginx + Gunicorn

upstream flask_app {
    # Single worker
    server 127.0.0.1:8000;
    
    # Or multiple workers
    # server 127.0.0.1:8000;
    # server 127.0.0.1:8001;
    # server 127.0.0.1:8002;
    
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name flask.example.com;
    
    # Logging
    access_log /var/log/nginx/flask.access.log;
    error_log /var/log/nginx/flask.error.log;
    
    # Max body size
    client_max_body_size 50M;
    
    # 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;
    
    # Proxy to Flask
    location / {
        proxy_pass http://flask_app;
        
        # Headers
        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;
        
        # HTTP version for keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffering
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;
    }
    
    # Static files (if served separately)
    location /static/ {
        alias /var/www/flask/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Favicon
    location = /favicon.ico {
        access_log off;
        log_not_found off;
    }
}

3.4. Systemd Service for Gunicorn

# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn instance for Flask application
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/flask
Environment="PATH=/var/www/flask/venv/bin"
ExecStart=/var/www/flask/venv/bin/gunicorn \
    --config /var/www/flask/gunicorn_config.py \
    --bind 127.0.0.1:8000 \
    app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=always

[Install]
WantedBy=multi-user.target

Start service:

sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn

3.5. Django Application

Nginx + Gunicorn for Django:

upstream django_app {
    server unix:/var/www/django/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    listen [::]:80;
    server_name django.example.com;
    
    # Logging
    access_log /var/log/nginx/django.access.log;
    error_log /var/log/nginx/django.error.log;
    
    # Max body size
    client_max_body_size 100M;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # Static files
    location /static/ {
        alias /var/www/django/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Media files
    location /media/ {
        alias /var/www/django/media/;
        expires 1y;
        add_header Cache-Control "public";
    }
    
    # Proxy to Django
    location / {
        proxy_pass http://django_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;
        
        proxy_redirect off;
        proxy_buffering off;
        
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Gunicorn systemd service for Django:

# /etc/systemd/system/gunicorn-django.service
[Unit]
Description=Gunicorn daemon for Django project
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/django
Environment="PATH=/var/www/django/venv/bin"
ExecStart=/var/www/django/venv/bin/gunicorn \
    --workers 4 \
    --bind unix:/var/www/django/gunicorn.sock \
    --timeout 60 \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log \
    myproject.wsgi:application

ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=always

[Install]
WantedBy=multi-user.target

4. Ruby on Rails với Puma

4.1. Puma Configuration

Puma config file:

# config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count

port ENV.fetch("PORT") { 3000 }

environment ENV.fetch("RAILS_ENV") { "production" }

# Use socket
bind "unix:///var/www/rails/tmp/sockets/puma.sock"

# Or use TCP
# bind "tcp://127.0.0.1:3000"

workers ENV.fetch("WEB_CONCURRENCY") { 2 }

preload_app!

on_worker_boot do
  ActiveRecord::Base.establish_connection
end

plugin :tmp_restart

4.2. Nginx + Puma

upstream rails_app {
    server unix:/var/www/rails/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    listen [::]:80;
    server_name rails.example.com;
    root /var/www/rails/public;
    
    # Logging
    access_log /var/log/nginx/rails.access.log;
    error_log /var/log/nginx/rails.error.log;
    
    # Max body size
    client_max_body_size 100M;
    
    # Try static files first
    try_files $uri/index.html $uri @rails_app;
    
    # Proxy to Rails
    location @rails_app {
        proxy_pass http://rails_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;
        
        proxy_redirect off;
        
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    # Static assets
    location ~* ^/assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # Cable (ActionCable for WebSockets)
    location /cable {
        proxy_pass http://rails_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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/rails/public;
    }
}

4.3. Systemd Service for Puma

# /etc/systemd/system/puma.service
[Unit]
Description=Puma HTTP Server for Rails
After=network.target

[Service]
Type=notify
User=deploy
Group=deploy
WorkingDirectory=/var/www/rails
Environment="RAILS_ENV=production"
Environment="PATH=/var/www/rails/.rbenv/shims:/usr/local/bin:/usr/bin:/bin"

ExecStart=/var/www/rails/.rbenv/shims/bundle exec puma -C /var/www/rails/config/puma.rb
ExecReload=/bin/kill -USR1 $MAINPID

Restart=always
RestartSec=1

StandardOutput=append:/var/log/puma/stdout.log
StandardError=append:/var/log/puma/stderr.log

[Install]
WantedBy=multi-user.target

5. Docker Containers

5.1. Nginx as Reverse Proxy for Docker Containers

Docker Compose example:

# docker-compose.yml
version: '3.8'

services:
  # Node.js app
  nodejs-app:
    build: ./nodejs
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: always
  
  # Python app
  python-app:
    build: ./python
    ports:
      - "8000:8000"
    environment:
      - FLASK_ENV=production
    restart: always
  
  # Nginx reverse proxy
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/sites:/etc/nginx/sites-enabled:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - nodejs-app
      - python-app
    restart: always

Nginx configuration for Docker:

# nginx/sites/default.conf
upstream nodejs {
    server nodejs-app:3000;
}

upstream python {
    server python-app:8000;
}

server {
    listen 80;
    server_name example.com;
    
    # Node.js app
    location /api/node/ {
        proxy_pass http://nodejs/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Python app
    location /api/python/ {
        proxy_pass http://python/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

5.2. Docker Network Configuration

# docker-compose.yml with custom network
version: '3.8'

networks:
  app-network:
    driver: bridge

services:
  backend:
    image: myapp:latest
    networks:
      - app-network
    expose:
      - "8080"
  
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    networks:
      - app-network
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

Nginx config:

upstream backend {
    server backend:8080;
}

server {
    listen 80;
    
    location / {
        proxy_pass http://backend;
    }
}

6. WebSocket Proxying

6.1. WebSocket Configuration

upstream websocket_backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name ws.example.com;
    
    # WebSocket location
    location /ws {
        proxy_pass http://websocket_backend;
        
        # WebSocket specific headers
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Standard headers
        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;
        
        # Timeouts (important for WebSockets)
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        
        # Disable buffering
        proxy_buffering off;
    }
}

6.2. Socket.IO Configuration

upstream socketio {
    ip_hash;  # Important for sticky sessions
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

server {
    listen 80;
    server_name socketio.example.com;
    
    location / {
        proxy_pass http://socketio;
        
        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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
        
        proxy_buffering off;
        proxy_cache_bypass $http_upgrade;
    }
}

6.3. Complete WebSocket Example

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream ws_backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    listen 443 ssl http2;
    server_name ws.example.com;
    
    ssl_certificate /etc/letsencrypt/live/ws.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;
    
    # Regular HTTP traffic
    location / {
        proxy_pass http://ws_backend;
        
        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 traffic
    location /socket {
        proxy_pass http://ws_backend;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_read_timeout 86400;
        proxy_send_timeout 86400;
        proxy_buffering off;
    }
}

7. gRPC Proxying

7.1. gRPC Configuration

upstream grpc_backend {
    server 127.0.0.1:50051;
    server 127.0.0.1:50052;
}

server {
    listen 443 ssl http2;
    server_name grpc.example.com;
    
    ssl_certificate /etc/letsencrypt/live/grpc.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/grpc.example.com/privkey.pem;
    
    # gRPC location
    location / {
        grpc_pass grpc://grpc_backend;
        
        # Error handling
        error_page 502 = /error502grpc;
    }
    
    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

7.2. gRPC with SSL

upstream grpc_ssl_backend {
    server 127.0.0.1:50051;
}

server {
    listen 443 ssl http2;
    server_name grpc.example.com;
    
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    location / {
        grpc_pass grpcs://grpc_ssl_backend;
        
        grpc_ssl_certificate /etc/nginx/ssl/client.crt;
        grpc_ssl_certificate_key /etc/nginx/ssl/client.key;
        grpc_ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
        
        grpc_set_header Host $host;
        grpc_set_header X-Real-IP $remote_addr;
    }
}

8. Complete Production Setup

8.1. Multi-Application Environment

# /etc/nginx/nginx.conf
http {
    # Upstreams
    
    # PHP-FPM for WordPress
    upstream php_wordpress {
        server unix:/var/run/php/php8.1-fpm.sock;
    }
    
    # Node.js API
    upstream nodejs_api {
        least_conn;
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
        keepalive 64;
    }
    
    # Python Flask
    upstream python_app {
        server unix:/var/www/flask/gunicorn.sock;
    }
    
    # Rails application
    upstream rails_app {
        server unix:/var/www/rails/tmp/sockets/puma.sock;
    }
    
    # WebSocket server
    upstream websocket {
        ip_hash;
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }
    
    # Include site configs
    include /etc/nginx/sites-enabled/*;
}

WordPress site:

# /etc/nginx/sites-available/wordpress.conf
server {
    listen 443 ssl http2;
    server_name blog.example.com;
    root /var/www/wordpress;
    index index.php;
    
    ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass php_wordpress;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Node.js API:

# /etc/nginx/sites-available/api.conf
server {
    listen 443 ssl http2;
    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;
    
    location / {
        proxy_pass http://nodejs_api;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Python app:

# /etc/nginx/sites-available/python.conf
server {
    listen 443 ssl http2;
    server_name app.example.com;
    
    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    
    location / {
        proxy_pass http://python_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;
    }
    
    location /static/ {
        alias /var/www/flask/static/;
        expires 1y;
    }
}

Tổng kết

Trong bài này, bạn đã học:

  • ✅ PHP-FPM configuration cho WordPress/Laravel
  • ✅ Nginx + Node.js với PM2
  • ✅ Python applications với Gunicorn/uWSGI
  • ✅ Ruby on Rails với Puma
  • ✅ Docker container integration
  • ✅ WebSocket proxying
  • ✅ gRPC proxying
  • ✅ Multi-application production setup

Key takeaways:

  • Use appropriate process managers (PHP-FPM, PM2, Gunicorn, Puma)
  • Configure proper timeouts và buffering
  • Enable keepalive connections
  • Use Unix sockets khi possible (faster than TCP)
  • Implement health checks
  • Monitor application performance

Bài tiếp theo: Monitoring và Logging - access logs analysis, error tracking, Prometheus integration, ELK stack, real-time monitoring, alerting và dashboard creation để maintain healthy production environments.

Nginx ApplicationStack PHPFPM NodeJS python Gunicorn uWSGI Ruby Puma Docker

Đánh dấu hoàn thành (Bài 11: Nginx với Application Stack trong NGINX)