Chuyển đến nội dung chính

第11課:NGINXとアプリケーションスタック

NGINXとアプリケーションスタックの統合に関するレッスン——WordPress/Laravel向けPHP-FPM設定、Nginx + Node.js(PM2)、Pythonアプリケーション(uWSGI/Gunicorn)、PumaによるRuby on Rails、Dockerコンテナ、WebSocketプロキシ、gRPC。

🔒 DevSecOps — 第11課 Bài 11: Nginx với Application Stack trong NGINX

NGINXの基礎から応用まで

第4部:アプリケーションスタック&監視

xdev.asia

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. PumaによるRuby on Rails

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

}


まとめ

このレッスンで学んだこと:

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

重要なポイント:

  • 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

次のレッスン: Monitoring và Logging - access logs analysis, error tracking, Prometheus integration, ELK stack, real-time monitoring, alerting và dashboard creation để maintain healthy production environments.