-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnginx.conf.template
More file actions
171 lines (153 loc) · 7.37 KB
/
Copy pathnginx.conf.template
File metadata and controls
171 lines (153 loc) · 7.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# HomelabARR Frontend Nginx Configuration
server {
listen 8080;
listen [::]:8080;
server_name localhost;
server_tokens off;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Security headers — must be repeated in any location block that uses add_header
# (nginx location-level add_header overrides server-level)
set $csp "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https://cdn.jsdelivr.net https://raw.githubusercontent.com; font-src 'self' data:; script-src 'self' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://cloudflareinsights.com; object-src 'none'; upgrade-insecure-requests; report-to csp-endpoint; report-uri /api/csp-report";
location /icons/ {
expires 1h;
add_header Cache-Control "public, max-age=3600, must-revalidate" always;
add_header Content-Security-Policy $csp always;
add_header Reporting-Endpoints 'csp-endpoint="/api/csp-report"' always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-site" always;
add_header X-XSS-Protection "0" always;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable" always;
add_header Content-Security-Policy $csp always;
add_header Reporting-Endpoints 'csp-endpoint="/api/csp-report"' always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-site" always;
add_header X-XSS-Protection "0" always;
}
location /health {
access_log off;
default_type text/plain;
return 200 "healthy\n";
}
# Legacy /security.txt → redirect to well-known (RFC 9116 §3)
location = /security.txt {
return 301 /.well-known/security.txt;
}
# robots.txt — explicit bot directives
location = /robots.txt {
default_type text/plain;
try_files /robots.txt =404;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
}
# RFC 8615 + RFC 8959 — password managers deep-link here
location = /.well-known/change-password {
return 302 /settings;
}
# humans.txt — team credit
location = /humans.txt {
default_type text/plain;
try_files /humans.txt =404;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
}
# Analytics proxy
location = /analytics.js {
proxy_pass https://analytics.mjashley.com/script.js;
proxy_set_header Host analytics.mjashley.com;
proxy_ssl_server_name on;
proxy_hide_header Set-Cookie;
proxy_ignore_headers Set-Cookie;
expires 1h;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
}
# Umami analytics event proxy (matches before /api/ catch-all)
location = /api/send {
proxy_pass https://analytics.mjashley.com/api/send;
proxy_set_header Host analytics.mjashley.com;
proxy_ssl_server_name on;
proxy_hide_header Set-Cookie;
proxy_ignore_headers Set-Cookie;
}
# API proxy to backend
location /api/ {
resolver 127.0.0.11 valid=10s ipv6=off;
set $backend ${BACKEND_URL};
rewrite ^/api/(.*) /$1 break;
proxy_pass $backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}
# SPA fallback — static files served directly; unknown paths get 404 + index.html
# React Router handles client-side display; scanners see the correct status code
error_page 404 =404 /index.html;
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
add_header Content-Security-Policy $csp always;
add_header Reporting-Endpoints 'csp-endpoint="/api/csp-report"' always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-site" always;
add_header X-XSS-Protection "0" always;
}
# Honey: CMS scanner probes — proxy to backend so honey.js emits audit events
location ~* ^/(wp-login\.php|wp-admin|xmlrpc\.php|phpmyadmin|phpMyAdmin|admin\.php|administrator|manager/html|shell\.php|cmd\.jsp|c99\.php) {
resolver 127.0.0.11 valid=10s ipv6=off;
set $backend ${BACKEND_URL};
proxy_pass $backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Honey-Edge "cms-scanner";
}
location ^~ /.well-known/ {
root /usr/share/nginx/html;
default_type text/plain;
allow all;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
}
# Honey: dotfile probes — proxy to backend so honey.js emits audit events
location ~ /\. {
resolver 127.0.0.11 valid=10s ipv6=off;
set $backend ${BACKEND_URL};
proxy_pass $backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Honey-Edge "dotfile";
}
}