Running Nginx with CleanStart
CleanStart provides production-ready Nginx base images (cleanstart/nginx) with Nginx 1.24 and 1.25 versions including current security patches. These images are pre-configured for reverse proxy deployments with comprehensive TLS and SSL support, transparent compression, and flexible caching enabled. They are fully compatible with Kubernetes ingress controllers for orchestrated environments.
Quick Start: Simple Reverse Proxy
Step 1: Create Nginx Configuration
Create nginx.conf:
user nginx;worker_processes auto;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid; events { worker_connections 4096; use epoll;} http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 20M; # Gzip compression gzip on; gzip_vary 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; # Upstream backend upstream backend { server backend1:8080 max_fails=3 fail_timeout=30s; server backend2:8080 max_fails=3 fail_timeout=30s; keepalive 32; } server { listen 80; server_name _; # Redirect to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name example.com; # SSL certificates ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; # Strong SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 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; location / { proxy_pass http://backend; 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; proxy_set_header X-Forwarded-Proto $scheme; # 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 { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } # Metrics for monitoring location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; allow 10.0.0.0/8; deny all; } }}Step 2: Create Dockerfile
FROM cleanstart/nginx:1.25 # Copy configurationCOPY nginx.conf /etc/nginx/nginx.conf # Create certificate directoryRUN mkdir -p /etc/nginx/certs # Create non-root user (already done in base, but ensure ownership)RUN chown -R nginx:nginx /etc/nginx # Health checkHEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1 USER nginxEXPOSE 80 443 CMD ["nginx", "-g", "daemon off;"]Step 3: Build and Run
# Generate self-signed certificate for testingopenssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt \ -days 365 -nodes -subj "/CN=example.com" # Create nginx directorymkdir -p nginx/certscp server.key nginx/certs/cp server.crt nginx/certs/cp nginx.conf nginx/ # Buildcd nginxdocker build -t my-nginx:latest . # Run with backend servicedocker run -d --name my-nginx -p 80:80 -p 443:443 my-nginx:latestDocker Compose: Complete Setup
version: '3.9' services: backend-1: image: my-backend-app:latest container_name: backend-1 ports: - "8081:8080" networks: - app-network backend-2: image: my-backend-app:latest container_name: backend-2 ports: - "8082:8080" networks: - app-network nginx: image: cleanstart/nginx:1.25 container_name: my-nginx volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro ports: - "80:80" - "443:443" depends_on: - backend-1 - backend-2 healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 10s timeout: 3s retries: 3 networks: - app-network networks: app-network: driver: bridgeDeploy: docker-compose up -d
Advanced: Load Balancing Strategies
Round Robin (Default)
upstream backend { server backend1:8080; server backend2:8080; server backend3:8080;}Least Connections
upstream backend { least_conn; server backend1:8080; server backend2:8080;}IP Hash (Session Stickiness)
upstream backend { ip_hash; server backend1:8080; server backend2:8080;}Weighted Load Balancing
upstream backend { server backend1:8080 weight=5; # 50% traffic server backend2:8080 weight=3; # 30% traffic server backend3:8080 weight=2; # 20% traffic}Caching Configuration
# Cache zoneproxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off; server { location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { proxy_cache my_cache; proxy_cache_valid 200 60m; proxy_cache_key "$scheme$proxy_host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; } location / { proxy_cache my_cache; proxy_cache_valid 200 10m; proxy_cache_key "$scheme$proxy_host$request_uri$request_body"; proxy_cache_bypass $http_pragma $http_authorization; add_header X-Cache-Status $upstream_cache_status; proxy_pass http://backend; }}Rate Limiting
# Define rate limit zonelimit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m; server { # Protect login endpoint location /api/login { limit_req zone=login_limit burst=10 nodelay; proxy_pass http://backend; } # Limit API requests location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://backend; }}TLS Configuration
Generate Certificates with Let's Encrypt
# Using Certbotdocker run --rm \ -v /etc/letsencrypt:/etc/letsencrypt \ -v /var/lib/letsencrypt:/var/lib/letsencrypt \ certbot/certbot certonly \ -d example.com \ --manual \ --preferred-challenges httpModern TLS Settings
server { listen 443 ssl http2; listen [::]:443 ssl http2; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # Strong configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_stapling on; ssl_stapling_verify on; # HSTS add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;}Kubernetes Ingress
Simple Ingress Example
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: app-ingress annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod"spec: ingressClassName: nginx tls: - hosts: - example.com secretName: app-tls rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: app-backend port: number: 8080 - path: /api pathType: Prefix backend: service: name: api-backend port: number: 8000Deploy: kubectl apply -f ingress.yaml
Monitoring
Access Logs
docker exec my-nginx tail -f /var/log/nginx/access.logError Logs
docker exec my-nginx tail -f /var/log/nginx/error.logMetrics Endpoint
curl http://localhost/nginx_status # Output example# Active connections: 5# server accepts handled requests# 15 15 15# Reading: 0 Writing: 1 Waiting: 4Security Best Practices
Disable Server Information Disclosure
server_tokens off;Security Headers (Complete)
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;add_header Content-Security-Policy "default-src 'self'" always;add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;Limit Request Size
client_max_body_size 10M;client_body_buffer_size 128k;Log Sensitive Data Protection
log_format sanitized '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "HIDDEN"';access_log /var/log/nginx/access.log sanitized;Performance Tuning
Worker Processes
Worker process configuration affects how Nginx distributes incoming connections across system resources. The worker_processes parameter can be set to auto to have Nginx automatically detect and use the number of available CPU cores, which provides optimal utilization for most systems. The worker_connections parameter defines how many simultaneous connections each worker process can handle, typically set to 4096 for production deployments handling moderate to high traffic.
worker_processes auto; # Auto-detect number of CPUsworker_connections 4096; # Per-worker limitCompression Levels
gzip on;gzip_comp_level 6; # Balance between speed and ratio (1-9)gzip_min_length 1000; # Don't compress small filesTroubleshooting
502 Bad Gateway errors typically indicate that Nginx is unable to reach the backend services—verify that your backend health checks are passing and that the upstream configuration correctly specifies all available servers. Connection refused errors usually mean the backend service is not running or is not accessible from the Nginx container—check network connectivity and ensure the service is listening on the expected port. SSL errors often stem from incorrect certificate paths or insufficient file permissions—verify that certificate files exist in the expected locations and that the Nginx process has read access. Slow response times suggest upstream performance issues or missing caching directives—monitor backend latency, enable appropriate caching headers, and review whether static assets can be cached more aggressively.
Image Options
Image | Use Case |
|---|---|
| Stable version (recommended) |
| Legacy support |
CleanStart Production Hardening
Read-Only Root Filesystem
Enforce immutable infrastructure with read-only root, with writable paths for Nginx logs and cache:
apiVersion: v1kind: Podmetadata: name: nginxspec: containers: - name: nginx image: cleanstart/nginx:1.25 securityContext: readOnlyRootFilesystem: true volumeMounts: - name: cache mountPath: /var/cache/nginx - name: run mountPath: /var/run/nginx - name: tmp mountPath: /tmp - name: logs mountPath: /var/log/nginx volumes: - name: cache emptyDir: {} - name: run emptyDir: {} - name: tmp emptyDir: {} - name: logs emptyDir: {}Shell-Less ENTRYPOINT
Remove shell for attack surface reduction. Update your Dockerfile:
FROM cleanstart/nginx:1.25 # Copy custom Nginx configurationCOPY nginx.conf /etc/nginx/nginx.confCOPY conf.d/ /etc/nginx/conf.d/ # Declarative Image Builder: Use cleanimg-init as PID 1ENTRYPOINT ["/cleanimg-init", "--"]CMD ["nginx", "-g", "daemon off;"]cleanimg-customize: Nginx Security Headers
Inject production security and hardening headers:
FROM cleanstart/nginx:1.25 COPY nginx.conf /etc/nginx/nginx.conf # Create custom server block with security headersRUN echo 'add_header X-Frame-Options "SAMEORIGIN" always;' >> /etc/nginx/conf.d/security.conf && \ echo 'add_header X-Content-Type-Options "nosniff" always;' >> /etc/nginx/conf.d/security.conf && \ echo 'add_header X-XSS-Protection "1; mode=block" always;' >> /etc/nginx/conf.d/security.conf && \ echo 'add_header Referrer-Policy "strict-origin-when-cross-origin" always;' >> /etc/nginx/conf.d/security.conf ENTRYPOINT ["/cleanimg-init", "--"]CMD ["nginx", "-g", "daemon off;"]Security Context
Complete Kubernetes securityContext for hardened Nginx containers:
apiVersion: apps/v1kind: Deploymentmetadata: name: nginxspec: template: spec: securityContext: fsGroup: 101 seccompProfile: type: RuntimeDefault containers: - name: nginx image: cleanstart/nginx:1.25 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 101 runAsGroup: 101 capabilities: drop: - ALL add: - NET_BIND_SERVICE resources: requests: cpu: 100m memory: 64Mi limits: cpu: 500m memory: 256MiNext Steps
Advanced Nginx Configuration, Nginx Performance Tuning, and Kubernetes Ingress Controller.
