-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy path.nginx.conf.example
More file actions
368 lines (327 loc) · 14.7 KB
/
Copy path.nginx.conf.example
File metadata and controls
368 lines (327 loc) · 14.7 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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# ========================================
# Pinakes - Nginx Configuration Example
# ========================================
#
# This file shows how to configure Nginx for Pinakes.
# Copy this to your Nginx sites-available directory and modify as needed.
#
# IMPORTANT: Document root should point to the /public/ directory!
#
# For production: Replace example.com with your domain
# For development: Use localhost
#
# ========================================
server {
listen 80;
listen [::]:80;
# Replace with your domain
server_name example.com;
# IMPORTANT: Document root must point to the PUBLIC directory
root /var/www/pinakes/public;
index index.php;
charset utf-8;
# Max upload size (adjust as needed for your library)
client_max_body_size 100M;
# ========================================
# Logging
# ========================================
access_log /var/log/nginx/pinakes_access.log;
error_log /var/log/nginx/pinakes_error.log;
# ========================================
# Performance: gzip compression
# ========================================
# Mirrors the public/.htaccess `# === Pinakes performance block ===` so
# Nginx- and Apache-hosted installs behave identically. Brotli requires
# the third-party ngx_brotli module — uncomment the gzip_static/brotli
# blocks below if you've built nginx with those modules.
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain text/css text/xml text/javascript
application/javascript application/x-javascript
application/json application/ld+json
application/xml application/rss+xml application/atom+xml application/xhtml+xml
image/svg+xml font/ttf font/otf font/woff;
# If nginx was built with --with-http_gzip_static_module, serve precompressed
# .gz files when present (faster than runtime compression).
# gzip_static on;
# If ngx_brotli is installed (https://github.com/google/ngx_brotli):
# brotli on;
# brotli_comp_level 4;
# brotli_types text/plain text/css application/json application/javascript
# text/xml application/xml application/xml+rss text/javascript
# image/svg+xml font/ttf font/otf;
# ========================================
# Security Headers
# ========================================
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Uncomment and adjust CSP for production:
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: https: http: blob:; connect-src 'self'; frame-src 'self' https://www.openstreetmap.org; frame-ancestors 'self';" always;
# ========================================
# Block access to sensitive/hidden files
# ========================================
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# ========================================
# Plugin-served assets (MUST precede the generic static block)
# ========================================
# Plugins such as digital-library and archives stream their own CSS/JS
# through Slim routes (/plugins/<name>/assets/<type>/<file>). These files
# don't exist on disk — PHP reads them from the plugin package and sets
# its own Cache-Control header (typically `public, max-age=31536000`
# because plugin asset paths are versioned by the plugin manifest).
#
# If the generic static location below catches these requests first, its
# `add_header Cache-Control "public, max-age=2592000, must-revalidate"`
# would stack on top of the plugin's header, sending the browser two
# conflicting values.
# Route them straight to PHP so only the plugin's header reaches the wire.
location ^~ /plugins/ {
try_files $uri /index.php?$query_string;
}
# ========================================
# Static assets with caching
# ========================================
# Note: Security headers must be repeated here because Nginx doesn't inherit
# add_header directives from parent context when add_header is used in a location
#
# Miss behaviour: hard 404. The plugin-asset location above already
# forwards `/plugins/*` to PHP; `/uploads/*` has its own block below;
# anything else with these extensions that isn't on disk is a genuine
# 404, not something PHP needs to answer. Falling back to index.php
# here would waste a PHP-FPM worker per bot/scanner probe and fill
# app logs with noise.
#
# Caching strategy: Pinakes ships every CSS/JS/font reference with a
# ?v=X.Y.Z querystring busted at each release (see HtmlHelper::asset).
# Versioned assets are therefore safe to mark immutable+1y — the URL
# itself changes on every release, so stale caches never bite back.
# Content images (covers, uploaded media) are NOT versioned and get a
# conservative 1-month TTL without `immutable`.
#
# Why two locations: `immutable` is correct for fingerprinted assets
# but wrong for raw content. Two regexes split the responsibility cleanly.
location ~* \.(css|js|woff|woff2|ttf|otf|eot|ico)$ {
# Disable the auto-emitted Cache-Control from `expires` and write a
# single explicit header. `expires 1y` would synthesise its own
# `Cache-Control: max-age=31536000`; stacked on top of our
# add_header that would send two ambiguous values.
expires off;
# No `always` flag on Cache-Control: nginx then applies this header
# only to 2xx/3xx responses. A transient 404 emitted by `try_files
# ... =404` below must NOT be cached for a year — the next deploy
# or upload should let users recover. Security headers keep
# `always` intentionally (they need to be present on error pages
# too, defense-in-depth against MIME sniffing / clickjacking).
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
try_files $uri =404;
}
location ~* \.(png|jpg|jpeg|gif|webp|svg|mp3|mp4|webm|pdf|epub)$ {
expires off;
add_header Cache-Control "public, max-age=2592000";
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
try_files $uri =404;
}
# ========================================
# Private uploaded content (issue #160)
# ========================================
# Digital-library files, archive documents and generic storage must NOT be
# served straight from disk — they go through the app so PrivateModeMiddleware
# (and any future per-file auth) governs access. These `^~` prefixes are
# longer than `^~ /uploads/` below, so nginx matches them first.
# ProtectedUploadController streams the bytes (with Range support).
location ^~ /uploads/digital/ { rewrite ^ /index.php last; }
location ^~ /uploads/archives/documents/ { rewrite ^ /index.php last; }
location ^~ /uploads/storage/ { rewrite ^ /index.php last; }
# ========================================
# Uploads directory (PUBLIC only — covers, events, CMS, branding)
# ========================================
location ^~ /uploads/ {
# Cache uploaded media (book covers, user uploads). The `^~ /uploads/`
# prefix takes precedence over the `~* \.(png|jpg|...)$` regex location
# above (per nginx location-matching rules), so without this explicit
# add_header /uploads/* files would be served with NO Cache-Control at
# all on nginx — asymmetric to the Apache .htaccess behaviour where
# mod_headers FilesMatch applies globally. Match the regex location's
# 1-month TTL without `immutable` (content media can change).
# `always` because we want this header on 4xx errors too (a 404 cover
# cached briefly is fine — they're rare and the next request gets the
# right answer).
add_header Cache-Control "public, max-age=2592000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# Serve uploaded files directly
try_files $uri =404;
# CORS headers for media streaming (Digital Library plugin)
# Note: Security headers must be repeated here (Nginx doesn't inherit add_header)
location ~* \.(mp3|m4a|ogg|wav|mp4|webm|ogv)$ {
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range, Content-Type" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range, Accept-Ranges" always;
add_header Accept-Ranges bytes always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
try_files $uri =404;
}
}
# ========================================
# Installer (accessible at /installer/)
# ========================================
# SECURITY: Remove or comment out this block after installation is complete.
# Access is restricted to localhost only as a safety measure.
location ^~ /installer/ {
# Only allow access from localhost (loopback)
allow 127.0.0.1;
allow ::1;
deny all;
alias /var/www/pinakes/installer/;
index index.php;
# Static assets for installer
location ~* ^/installer/.*\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
}
# PHP processing for installer
location ~ ^/installer/.*\.php$ {
# Only allow index.php
if ($uri !~ "^/installer/index\.php$") {
return 403;
}
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
# Important: Use $request_filename with alias
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Route to installer index.php
try_files $uri $uri/ /installer/index.php?$query_string;
}
# ========================================
# PHP Processing
# ========================================
location ~ \.php$ {
# Only allow index.php for security
if ($fastcgi_script_name !~ "^/index\.php$") {
return 403;
}
# Adjust PHP-FPM socket/port to match your setup:
# - Debian/Ubuntu: unix:/var/run/php/php8.2-fpm.sock
# - CentOS/RHEL: unix:/var/run/php-fpm/www.sock
# - macOS (Homebrew): 127.0.0.1:9000
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Pass Authorization header (for API authentication)
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
# Increase timeouts for long operations (imports, backups)
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
# PHP settings (can also be set in php.ini)
fastcgi_param PHP_VALUE "upload_max_filesize=100M \n post_max_size=100M \n max_execution_time=300";
}
# ========================================
# Front Controller
# ========================================
location / {
# Try to serve file directly, fall back to index.php
try_files $uri $uri/ /index.php?$query_string;
}
}
# ========================================
# Installer Configuration (Separate Server Block)
# ========================================
# The installer runs from /installer/ directory outside public.
# After installation, you can remove or comment out this block.
#
# Access installer at: http://example.com:8889/
#
server {
# SECURITY: Limit installer to loopback interface only
# Never expose installer to public network
listen 127.0.0.1:8889;
listen [::1]:8889;
server_name localhost;
# Point to the installer directory (NOT public!)
root /var/www/pinakes/installer;
index index.php;
charset utf-8;
client_max_body_size 100M;
# Static assets for installer
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
}
# Block hidden files
location ~ /\. {
deny all;
}
# PHP processing for installer
location ~ \.php$ {
if ($fastcgi_script_name !~ "^/index\.php$") {
return 403;
}
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
# Route to front controller
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
# ========================================
# SSL/HTTPS Configuration (Recommended for Production)
# ========================================
# Uncomment and configure for production with SSL certificate.
# Use Let's Encrypt (certbot) for free certificates.
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
#
# server_name example.com;
#
# # SSL Certificate paths (Let's Encrypt example)
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
#
# # Modern SSL configuration
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# ssl_prefer_server_ciphers off;
#
# # HSTS (optional but recommended)
# add_header Strict-Transport-Security "max-age=63072000" always;
#
# # ... (copy the location blocks from the HTTP server above)
#
# root /var/www/pinakes/public;
# index index.php;
# # ... etc
# }
#
# # Redirect HTTP to HTTPS
# server {
# listen 80;
# listen [::]:80;
# server_name example.com;
# return 301 https://$server_name$request_uri;
# }