worker_processes auto; error_log /dev/stderr warn; pid /var/www/pixelfed/storage/nginx.pid; events { worker_connections 1024; use epoll; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # Configure temp paths that pixelfed user can write to client_body_temp_path /var/www/pixelfed/storage/nginx_temp/client_body; proxy_temp_path /var/www/pixelfed/storage/nginx_temp/proxy; fastcgi_temp_path /var/www/pixelfed/storage/nginx_temp/fastcgi; uwsgi_temp_path /var/www/pixelfed/storage/nginx_temp/uwsgi; scgi_temp_path /var/www/pixelfed/storage/nginx_temp/scgi; 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 /dev/stdout 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 application/atom+xml application/activity+json application/ld+json image/svg+xml; # HTTP server block (port 80) server { listen 80; server_name _; root /var/www/pixelfed/public; index index.php; charset utf-8; # 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; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.hcaptcha.com https://hcaptcha.com; style-src 'self' 'unsafe-inline' https://hcaptcha.com; img-src 'self' data: blob: https: http: https://imgs.hcaptcha.com; media-src 'self' https: http:; connect-src 'self' https://hcaptcha.com; font-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; frame-ancestors 'none';" always; # Hide nginx version server_tokens off; # Main location block location / { try_files $uri $uri/ /index.php?$query_string; } # Error handling - pass 404s to Laravel/Pixelfed (CRITICAL for routing) error_page 404 /index.php; # Favicon and robots location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } # PHP-FPM processing - simplified like official Pixelfed location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; # Let nginx ingress and Laravel config handle HTTPS detection # Optimized for web workload fastcgi_buffering on; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_read_timeout 300; fastcgi_connect_timeout 60; fastcgi_send_timeout 300; } # CSS and JS files - shorter cache for updates location ~* \.(css|js) { expires 7d; add_header Cache-Control "public, max-age=604800"; access_log off; try_files $uri $uri/ /index.php?$query_string; } # Font files - medium cache location ~* \.(woff|woff2|ttf|eot) { expires 30d; add_header Cache-Control "public, max-age=2592000"; access_log off; try_files $uri $uri/ /index.php?$query_string; } # Media files - long cache (user uploads don't change) location ~* \.(jpg|jpeg|png|gif|webp|avif|heic|mp4|webm|mov)$ { expires 1y; add_header Cache-Control "public, max-age=31536000"; access_log off; # Try local first, fallback to S3 CDN for media try_files $uri @media_fallback; } # Icons and SVG - medium cache location ~* \.(ico|svg) { expires 30d; add_header Cache-Control "public, max-age=2592000"; access_log off; try_files $uri $uri/ /index.php?$query_string; } # ActivityPub and federation endpoints location ~* ^/(\.well-known|api|oauth|outbox|following|followers) { try_files $uri $uri/ /index.php?$query_string; } # Health check endpoint location = /api/v1/instance { try_files $uri $uri/ /index.php?$query_string; } # Pixelfed mobile app endpoints location ~* ^/api/v1/(accounts|statuses|timelines|notifications) { try_files $uri $uri/ /index.php?$query_string; } # Pixelfed discover and search location ~* ^/(discover|search) { try_files $uri $uri/ /index.php?$query_string; } # Media fallback to CDN (if using S3) location @media_fallback { return 302 https://pm.keyboardvagabond.com$uri; } # Deny access to hidden files location ~ /\.(?!well-known).* { deny all; } # Block common bot/scanner requests location ~* (wp-admin|wp-login|phpMyAdmin|phpmyadmin) { return 444; } } # HTTPS server block (port 443) - for Cloudflare tunnel internal TLS server { listen 443 ssl; server_name _; root /var/www/pixelfed/public; index index.php; charset utf-8; # cert-manager generated SSL certificate for internal communication ssl_certificate /etc/ssl/certs/tls.crt; ssl_certificate_key /etc/ssl/private/tls.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # Security headers (same as HTTP block) add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.hcaptcha.com https://hcaptcha.com; style-src 'self' 'unsafe-inline' https://hcaptcha.com; img-src 'self' data: blob: https: http: https://imgs.hcaptcha.com; media-src 'self' https: http:; connect-src 'self' https://hcaptcha.com; font-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; frame-ancestors 'none';" always; # Hide nginx version server_tokens off; # Main location block location / { try_files $uri $uri/ /index.php?$query_string; } # Error handling - pass 404s to Laravel/Pixelfed (CRITICAL for routing) error_page 404 /index.php; # Favicon and robots location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } # PHP-FPM processing - same as HTTP block location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; # Set HTTPS environment for Laravel fastcgi_param HTTPS on; fastcgi_param SERVER_PORT 443; # Optimized for web workload fastcgi_buffering on; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_read_timeout 300; fastcgi_connect_timeout 60; fastcgi_send_timeout 300; } # Static file handling (same as HTTP block) location ~* \.(css|js) { expires 7d; add_header Cache-Control "public, max-age=604800"; access_log off; try_files $uri $uri/ /index.php?$query_string; } location ~* \.(woff|woff2|ttf|eot) { expires 30d; add_header Cache-Control "public, max-age=2592000"; access_log off; try_files $uri $uri/ /index.php?$query_string; } location ~* \.(jpg|jpeg|png|gif|webp|avif|heic|mp4|webm|mov)$ { expires 1y; add_header Cache-Control "public, max-age=31536000"; access_log off; try_files $uri @media_fallback; } location ~* \.(ico|svg) { expires 30d; add_header Cache-Control "public, max-age=2592000"; access_log off; try_files $uri $uri/ /index.php?$query_string; } # ActivityPub and federation endpoints location ~* ^/(\.well-known|api|oauth|outbox|following|followers) { try_files $uri $uri/ /index.php?$query_string; } # Health check endpoint location = /api/v1/instance { try_files $uri $uri/ /index.php?$query_string; } # Pixelfed mobile app endpoints location ~* ^/api/v1/(accounts|statuses|timelines|notifications) { try_files $uri $uri/ /index.php?$query_string; } # Pixelfed discover and search location ~* ^/(discover|search) { try_files $uri $uri/ /index.php?$query_string; } # Media fallback to CDN (if using S3) location @media_fallback { return 302 https://pm.keyboardvagabond.com$uri; } # Deny access to hidden files location ~ /\.(?!well-known).* { deny all; } # Block common bot/scanner requests location ~* (wp-admin|wp-login|phpMyAdmin|phpmyadmin) { return 444; } } }