Skip to main content
CleanStart

Knowledge Hub

Getting Started with Nginx on CleanStart

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:latest

Docker 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: bridge

Deploy: 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 http

Modern 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: 8000

Deploy: kubectl apply -f ingress.yaml

Monitoring

Access Logs

docker exec my-nginx tail -f /var/log/nginx/access.log

Error Logs

docker exec my-nginx tail -f /var/log/nginx/error.log

Metrics Endpoint

curl http://localhost/nginx_status # Output example# Active connections: 5# server accepts handled requests# 15 15 15# Reading: 0 Writing: 1 Waiting: 4

Security 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 limit

Compression Levels

gzip on;gzip_comp_level 6;               # Balance between speed and ratio (1-9)gzip_min_length 1000;            # Don't compress small files

Troubleshooting

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

cleanstart/nginx:1.25

Stable version (recommended)

cleanstart/nginx:1.24

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: 256Mi

Next Steps

Advanced Nginx Configuration, Nginx Performance Tuning, and Kubernetes Ingress Controller.