feat: Making it Dockerable
This commit is contained in:
107
nginx.conf
Normal file
107
nginx.conf
Normal file
@@ -0,0 +1,107 @@
|
||||
# Hardened nginx config for a static portfolio
|
||||
# Runs as user 'nginx' (non-root), listens on port 8080
|
||||
|
||||
worker_processes auto; # one worker per CPU core
|
||||
worker_rlimit_nofile 1024; # tight file-descriptor cap
|
||||
|
||||
# Log to stdout/stderr so Docker can collect them
|
||||
error_log /dev/stderr warn;
|
||||
pid /tmp/nginx.pid; # writable by non-root user
|
||||
|
||||
events {
|
||||
worker_connections 512;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
# ── Temp paths writable by the nginx user ───────────────────────────────
|
||||
# /tmp is a 1777 tmpfs — writable by any user, no subdirectory pre-creation needed
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
proxy_temp_path /tmp/proxy_temp;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
|
||||
access_log /dev/stdout combined;
|
||||
|
||||
# ── MIME types ──────────────────────────────────────────────────────────
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# ── Performance ─────────────────────────────────────────────────────────
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 30;
|
||||
keepalive_requests 100;
|
||||
|
||||
# ── Compression ─────────────────────────────────────────────────────────
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 5;
|
||||
gzip_types
|
||||
text/plain text/css text/javascript application/javascript
|
||||
application/json application/xml image/svg+xml
|
||||
font/woff font/woff2;
|
||||
|
||||
# ── Security headers ────────────────────────────────────────────────────
|
||||
server_tokens off; # hide nginx version
|
||||
|
||||
# ── Virtual host ────────────────────────────────────────────────────────
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security headers sent on every response
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||
add_header Content-Security-Policy
|
||||
"default-src 'self'; \
|
||||
script-src 'self' 'unsafe-inline'; \
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
|
||||
font-src 'self' https://fonts.gstatic.com; \
|
||||
img-src 'self' data:; \
|
||||
connect-src 'self'; \
|
||||
frame-ancestors 'none';"
|
||||
always;
|
||||
|
||||
# Cache static assets aggressively, HTML never
|
||||
location ~* \.(css|js|woff2?|ico|svg|png|jpg|jpeg|gif|webp)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
# Deny access to hidden files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Limit accepted HTTP methods
|
||||
if ($request_method !~ ^(GET|HEAD)$) {
|
||||
return 405;
|
||||
}
|
||||
|
||||
# Cap request body (nothing to POST to a static site)
|
||||
client_max_body_size 1k;
|
||||
|
||||
# Timeout tuning
|
||||
client_header_timeout 10s;
|
||||
client_body_timeout 10s;
|
||||
send_timeout 10s;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user