# /etc/nginx/sites-available/puffinoffset.com # 1) Redirect all HTTP to HTTPS, except the ACME challenge path server { listen 80; server_name puffinoffset.com; # Allow certbot to do HTTP-01 challenges location ^~ /.well-known/acme-challenge/ { root /var/www/html; # adjust if your webroot differs try_files $uri =404; } # Redirect everything else to HTTPS location / { return 301 https://$host$request_uri; } } # 2) HTTPS server block: reverse-proxy to your Docker app on localhost:3800 server { listen 443 ssl http2; server_name puffinoffset.com; # === SSL certs from Let's Encrypt === # ssl_certificate /etc/letsencrypt/live/puffinoffset.com/fullchain.pem; # ssl_certificate_key /etc/letsencrypt/live/puffinoffset.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; # from certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # from certbot # === Proxy for direct image requests from Wren API === location ~* ^/images/(.*)$ { proxy_pass https://www.wren.co/images/$1; proxy_ssl_server_name on; proxy_set_header Host www.wren.co; 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_buffers 16 4k; proxy_buffer_size 2k; # Add CORS headers for images add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'GET, OPTIONS' always; add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always; # Cache control for images expires 7d; add_header Cache-Control "public, max-age=604800"; # Handle OPTIONS requests for CORS preflight if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, OPTIONS'; add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization'; add_header Access-Control-Max-Age 1728000; add_header Content-Type 'text/plain charset=UTF-8'; add_header Content-Length 0; return 204; } } # === Proxy for Wren API requests === location /api/wren/ { proxy_pass https://www.wren.co/api/; proxy_ssl_server_name on; proxy_set_header Host www.wren.co; 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; # Add CORS headers for API requests add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always; add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always; # Handle OPTIONS requests for CORS preflight if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization'; add_header Access-Control-Max-Age 1728000; add_header Content-Type 'text/plain charset=UTF-8'; add_header Content-Length 0; return 204; } } # === Next.js Frontend QR Code API === # NOTE: This MUST come before the general /api/ backend location block location /api/qr-code/ { proxy_pass http://127.0.0.1:3800; proxy_http_version 1.1; 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; # CORS headers add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always; add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always; # API timeouts proxy_read_timeout 120; proxy_connect_timeout 120; proxy_send_timeout 120; # Handle OPTIONS for CORS preflight if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; add_header Access-Control-Max-Age 1728000; add_header Content-Length 0; return 204; } } # === Backend API - Stripe Webhooks (specific route, no trailing slash) === location = /api/webhooks/stripe { proxy_pass http://127.0.0.1:3801/api/webhooks/stripe; proxy_http_version 1.1; 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; # Stripe requires raw body for signature verification proxy_request_buffering off; # CORS headers add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'POST, OPTIONS' always; add_header Access-Control-Allow-Headers 'Content-Type, Stripe-Signature' always; } # === Backend API - All other API routes === location /api/ { proxy_pass http://127.0.0.1:3801/; proxy_http_version 1.1; 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; # CORS headers for API add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always; add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always; # API timeouts proxy_read_timeout 120; proxy_connect_timeout 120; proxy_send_timeout 120; # Handle OPTIONS for CORS preflight if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; add_header Access-Control-Max-Age 1728000; add_header Content-Length 0; return 204; } } # === Proxy all other traffic to Frontend === location / { proxy_pass http://127.0.0.1:3800; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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; # Add CORS headers for all responses add_header Access-Control-Allow-Origin '*' always; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always; add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization' always; # Increase timeouts for potentially slow API calls proxy_read_timeout 120; proxy_connect_timeout 120; proxy_send_timeout 120; } # === Additional common settings === # Increase client body size for file uploads if needed client_max_body_size 10M; # Enable compression for better performance gzip on; gzip_comp_level 5; gzip_min_length 256; gzip_proxied any; gzip_vary on; gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; # Optional: serve static assets directly if you ever add any here # location /static/ { # root /var/www/puffinoffset.com; # try_files $uri $uri/ =404; # } }