# Docker Registry v2 Nginx Configuration with Enhanced Security # Port 443: Unauthenticated pulls (GET requests only) # Port 4443: Authenticated operations (login, logout, push, delete, etc.) events { worker_connections 1024; use epoll; multi_accept on; } http { # Rate limiting with different zones for different operations limit_req_zone $binary_remote_addr zone=registry:10m rate=10r/s; limit_req_zone $binary_remote_addr zone=push:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s; # Connection limiting limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; # Upstream Docker Registry upstream registry { server localhost:5000; keepalive 32; # Health check keepalive_requests 100; keepalive_timeout 60s; } # HTTP server for unauthenticated pulls on port 443 server { listen 443 ssl http2; server_name _; # Connection limits limit_conn conn_limit_per_ip 10; # SSL Configuration - TLS 1.2/1.3 only with modern ciphers ssl_certificate /etc/registry/certs/registry.crt; ssl_certificate_key /etc/registry/certs/private/registry.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; # Security headers with HSTS 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 Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Client max body size for large image uploads client_max_body_size 2G; client_body_timeout 60s; client_header_timeout 60s; # Rate limiting for read operations limit_req zone=registry burst=20 nodelay; # Block all write operations explicitly if ($request_method !~ ^(GET|HEAD)$) { return 405 "Method Not Allowed"; } # Block suspicious user agents if ($http_user_agent ~* (bot|crawler|spider|scraper)) { return 403 "Forbidden"; } # Allow all GET requests to v2 API (Docker Registry itself will handle security) location /v2/ { proxy_pass http://registry; proxy_set_header Host $http_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_read_timeout 900; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_buffering off; proxy_request_buffering off; # Additional security headers for registry proxy_hide_header X-Powered-By; proxy_hide_header Server; } location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; add_header Cache-Control "no-cache, no-store, must-revalidate"; } # Block access to hidden files location ~ /\. { deny all; access_log off; log_not_found off; } # Block access to sensitive files location ~* \.(htaccess|htpasswd|ini|log|sh|sql|conf)$ { deny all; access_log off; log_not_found off; } location / { return 404; } access_log /var/log/nginx/registry_access.log; error_log /var/log/nginx/registry_error.log; } # HTTPS server for authenticated operations on port 4443 server { listen 4443 ssl http2; server_name _; # Connection limits limit_conn conn_limit_per_ip 5; # SSL Configuration - TLS 1.2/1.3 only with modern ciphers ssl_certificate /etc/registry/certs/registry.crt; ssl_certificate_key /etc/registry/certs/private/registry.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; # Security headers with HSTS 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 Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Client max body size for large image uploads client_max_body_size 2G; client_body_timeout 60s; client_header_timeout 60s; # Rate limiting for write operations limit_req zone=push burst=10 nodelay; # Rate limiting for login attempts location ~ ^/v2/.*/blobs/uploads/ { limit_req zone=login burst=3 nodelay; auth_basic "Docker Registry v2"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://registry; proxy_set_header Host $http_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_read_timeout 900; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_buffering off; proxy_request_buffering off; proxy_hide_header X-Powered-By; proxy_hide_header Server; } # Basic authentication for write operations location ~ ^/v2/.*$ { # Require auth for all v2 API operations auth_basic "Docker Registry v2"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://registry; proxy_set_header Host $http_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_read_timeout 900; proxy_connect_timeout 60; proxy_send_timeout 60; proxy_buffering off; proxy_request_buffering off; proxy_hide_header X-Powered-By; proxy_hide_header Server; } # Block access to hidden files location ~ /\. { deny all; access_log off; log_not_found off; } # Block access to sensitive files location ~* \.(htaccess|htpasswd|ini|log|sh|sql|conf)$ { deny all; access_log off; log_not_found off; } location / { return 404; } access_log /var/log/nginx/registry_auth_access.log; error_log /var/log/nginx/registry_auth_error.log; } }