11# Minimum Nginx version required: 1.13.0 (released Apr 25, 2017)
22# Please check your Nginx installation features the following modules via 'nginx -V':
3- # STANDARD HTTP MODULES: Core, Proxy, Rewrite, Access, Gzip, Headers, HTTP/2, Log, Real IP, SSL, Thread Pool, Upstream, AIO Multithreading.
3+ # STANDARD HTTP MODULES: Core, Proxy, Rewrite, Access, Gzip, Headers, HTTP/2, Log, Real IP, SSL, Thread Pool, Upstream, AIO Multithreading, Map .
44# THIRD PARTY MODULES: None.
55
66server {
77 listen 80;
88 listen [::]:80;
99 server_name ${WEBSERVER_HOST};
1010
11- location /.well-known/acme-challenge/ {
11+ location ^~ /.well-known/acme-challenge/ {
1212 default_type "text/plain";
1313 root /var/www/certbot;
1414 }
@@ -19,29 +19,87 @@ upstream backend {
1919 server ${PEERTUBE_HOST};
2020}
2121
22+ ##
23+ # ---------------------------------------------------------------------------
24+ # Horizontal scalability
25+ # ---------------------------------------------------------------------------
26+ #
27+ # Horizontal scalability allows to offload some API paths to `--role=secondary` processes.
28+ # See https://docs.joinpeertube.org/maintain/horizontal-scalability for more information.
29+ #
30+ # Leave this section untouched if you run a single PeerTube process.
31+ #
32+ # If you want to enable horizontal scalability, add one `server` line per secondary to both pools below
33+ # and keep `${PEERTUBE_HOST}` as a `backup` so reads survive every secondary going down.
34+ # For example:
35+ #
36+ # upstream secondary_pool {
37+ # server 127.0.0.1:9100;
38+ # server 127.0.0.1:9101;
39+ # server ${PEERTUBE_HOST} backup;
40+ # }
41+ #
42+ # `secondary_views_pool` handles view/watch-time tracking only. It's optional since the heartbeat can be spread to any process,
43+ # but it's faster to stick a heartbeat to a single process (because of local process caching).
44+ # If a CDN or another load balancer sits in front, set `set_real_ip_from` / `real_ip_header` accordingly,
45+ # otherwise every viewer hashes to the same secondary and PeerTube counts every view against the proxy IP.
46+ ##
47+
48+ upstream secondary_pool {
49+ server ${PEERTUBE_HOST};
50+ }
51+
52+ upstream secondary_views_pool {
53+ hash $remote_addr consistent;
54+
55+ server ${PEERTUBE_HOST};
56+ }
57+
58+ # Secondary servers only support a subset of API endpoints
59+ # Leave this section untouched, even if you don't use horizontal scalability
60+ #
61+ # Regexes are evaluated top to bottom, first match wins
62+ # method + path -> pool
63+ map $request_method$uri $peertube_pool {
64+ default backend;
65+
66+ # Read-only video endpoints served by `--role=secondary` processes
67+ "~^(GET|HEAD)/api/v1/videos/?$" secondary_pool;
68+ "~^(GET|HEAD)/api/v1/videos/(categories|licences|languages|privacies)$" secondary_pool;
69+ # Collections that live under /api/v1/videos/ but are not a video id: must be listed before the /api/v1/videos/<id> line below
70+ "~^(GET|HEAD)/api/v1/videos/(blacklist|comments|imports|ownership|live)(/|$)" backend;
71+ "~^(GET|HEAD)/api/v1/videos/[^/]+/?$" secondary_pool;
72+
73+ # View/watch-time tracking: sticky to one process per viewer
74+ "~^(PUT|POST)/api/v1/videos/[^/]+/(views|watching)$" secondary_views_pool;
75+ }
76+
2277server {
2378 listen 443 ssl http2;
2479 listen [::]:443 ssl http2;
80+
81+ # On nginx >= 1.25.1 `http2` as a `listen` parameter is deprecated and logs a warning on startup
82+ # If your nginx is recent enough, replace the two lines above by `listen 443 ssl;` + `listen [::]:443 ssl;`and add `http2 on;` here
2583 server_name ${WEBSERVER_HOST};
2684
85+ server_tokens off; # don't advertise the nginx version in responses and error pages
86+
2787 access_log /var/log/nginx/peertube.access.log; # reduce I/0 with buffer=10m flush=5m
2888 error_log /var/log/nginx/peertube.error.log;
2989
3090 ##
3191 # Certificates
32- # you need a certificate to run in production. see https://letsencrypt.org/
3392 ##
3493 ssl_certificate /etc/letsencrypt/live/${WEBSERVER_HOST}/fullchain.pem;
3594 ssl_certificate_key /etc/letsencrypt/live/${WEBSERVER_HOST}/privkey.pem;
3695
37- location ^~ ' /.well-known/acme-challenge' {
96+ location ^~ /.well-known/acme-challenge/ {
3897 default_type "text/plain";
3998 root /var/www/certbot;
4099 }
41100
42101 ##
43- # Security hardening (as of Nov 15, 2020)
44- # based on Mozilla Guideline v5.6
102+ # Security hardening (as of Nov 15, 2020) based on Mozilla Guideline v5.6
45103 ##
46104
47105 ssl_protocols TLSv1.2 TLSv1.3;
@@ -55,20 +113,48 @@ server {
55113
56114 ##
57115 # Application
116+ # ---------------------------------------------------------------------------
117+ #
118+ # IMPORTANT, read before editing a `location` below.
119+ #
120+ # `try_files /dev/null @named` performs an internal redirect that adds subtleties:
121+ # * `client_max_body_size` and the `add_header` that are read from the location matching the URI, i.e., the `location ~ ...` block.
122+ # * `proxy_*` directives and `add_header` on a proxied response are read from the final configuration, i.e. from the `@named` block
58123 ##
59124
60125 location @api {
61126 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
62127 proxy_set_header Host $host; # Use $http_host if you use a non standard HTTP port
63128 proxy_set_header X-Real-IP $remote_addr;
64129
65- client_max_body_size 100k; # default is 1M
130+ proxy_connect_timeout 10m;
131+ proxy_send_timeout 10m;
132+ proxy_read_timeout 10m;
133+ send_timeout 10m;
134+
135+ proxy_pass http://$peertube_pool;
136+ }
137+
138+ # Same as @api, but streams the request body go directly to PeerTube instead of being written to /var/lib/nginx first
139+ # Used by the upload endpoints, which are always served by the primary
140+ location @api_unbuffered {
141+ # Required for `proxy_request_buffering off`
142+ proxy_http_version 1.1;
143+
144+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
145+ proxy_set_header Host $host; # Use $http_host if you use a non standard HTTP port
146+ proxy_set_header X-Real-IP $remote_addr;
66147
67148 proxy_connect_timeout 10m;
68149 proxy_send_timeout 10m;
69150 proxy_read_timeout 10m;
70151 send_timeout 10m;
71152
153+ proxy_request_buffering off;
154+ # Only applies to chunked request bodies
155+ # A body with a Content-Length is checked against the `client_max_body_size` of the location that matched the URI, before we get here
156+ client_max_body_size 0;
157+
72158 proxy_pass http://backend;
73159 }
74160
@@ -77,17 +163,15 @@ server {
77163 }
78164
79165 location ~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$ {
80- client_max_body_size 0;
81- proxy_request_buffering off;
166+ client_max_body_size 0;
82167
83- try_files /dev/null @api ;
168+ try_files /dev/null @api_unbuffered ;
84169 }
85170
86171 location ~ ^/api/v1/users/[^/]+/imports/import-resumable$ {
87- client_max_body_size 0;
88- proxy_request_buffering off;
172+ client_max_body_size 0;
89173
90- try_files /dev/null @api ;
174+ try_files /dev/null @api_unbuffered ;
91175 }
92176
93177 location ~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$ {
@@ -98,22 +182,28 @@ server {
98182 # This data gets stored in /var/lib/nginx by default, so you may want to put this directory
99183 # on a dedicated filesystem.
100184 client_max_body_size 12G; # default is 1M
101- add_header X-File-Maximum-Size 8G always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
102- proxy_request_buffering off;
185+ add_header X-File-Maximum-Size 8G always; # inform the client of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
103186
104- try_files /dev/null @api ;
187+ try_files /dev/null @api_unbuffered ;
105188 }
106189
107190 location ~ ^/api/v1/runners/jobs/[^/]+/(update|success)$ {
108- client_max_body_size 0;
109- proxy_request_buffering off;
191+ client_max_body_size 0;
192+
193+ try_files /dev/null @api_unbuffered;
194+ }
195+
196+ # View tracking (PUT|POST /views and /watching)
197+ location ~ ^/api/v1/videos/[^/]+/(views|watching)$ {
198+ client_max_body_size 100k; # default is 1M
110199
111200 try_files /dev/null @api;
112201 }
113202
114- location ~ ^/api/v1/(videos|video-playlists|video-channels|users/me) {
203+ # Endpoints that accept an image (avatar, banner, thumbnail, preview, channel logo, instance logo)
204+ location ~ ^/api/v1/(videos|video-playlists|video-channels|users/me|config/instance-(avatar|banner|logo)) {
115205 client_max_body_size 12M; # default is 1M
116- add_header X-File-Maximum-Size 8M always; # inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
206+ add_header X-File-Maximum-Size 8M always; # inform the client of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
117207
118208 try_files /dev/null @api;
119209 }
@@ -152,14 +242,13 @@ server {
152242
153243 ##
154244 # Performance optimizations
155- # For extra performance please refer to https://github.com/denji/nginx-tuning
156245 ##
157246
158247 root /var/www/peertube/storage;
159248
160- # Enable compression for JS/CSS/HTML , for improved client load times.
161- # It might be nice to compress JSON/XML as returned by the API, but
162- # leaving that out to protect against potential BREACH attack.
249+ # Compress the static client assets and the public XML feeds , for improved client load times.
250+ #
251+ # `application/json` is deliberately left out to prevent BREACH (CVE-2013-3587)
163252 gzip on;
164253 gzip_vary on;
165254 gzip_types # text/html is always compressed by HttpGzipModule
@@ -169,7 +258,11 @@ server {
169258 font/opentype
170259 application/vnd.ms-fontobject
171260 image/svg+xml
172- application/xml;
261+ # PeerTube negotiates any of these for /feeds/* and /sitemap.xml
262+ text/xml
263+ application/xml
264+ application/rss+xml
265+ application/atom+xml;
173266 gzip_min_length 1000; # default is 20 bytes
174267 gzip_buffers 16 8k;
175268 gzip_comp_level 2; # default is 1
@@ -231,22 +324,18 @@ server {
231324 # Or this line with nginx < 1.17.0
232325 # set $limit_rate $peertube_limit_rate;
233326
327+ # These files are public and unauthenticated, so they can be read cross origin.
328+ add_header Access-Control-Allow-Origin '*' always;
329+ add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
330+ add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always;
331+ add_header Access-Control-Max-Age 1728000 always; # Preflight request can be cached 20 days
332+
333+ # Keep this `if` free of `add_header`: a taken `if` block replaces the whole add_header set of
334+ # the enclosing location, which would strip the CORS headers above from preflight responses.
234335 if ($request_method = 'OPTIONS') {
235- add_header Access-Control-Allow-Origin '*';
236- add_header Access-Control-Allow-Methods 'GET, OPTIONS';
237- add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
238- add_header Access-Control-Max-Age 1728000; # Preflight request can be cached 20 days
239- add_header Content-Type 'text/plain; charset=UTF-8';
240- add_header Content-Length 0;
241336 return 204;
242337 }
243338
244- if ($request_method = 'GET') {
245- add_header Access-Control-Allow-Origin '*';
246- add_header Access-Control-Allow-Methods 'GET, OPTIONS';
247- add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
248- }
249-
250339 # Enabling the sendfile directive eliminates the step of copying the data into the buffer
251340 # and enables direct copying data from one file descriptor to another.
252341 sendfile on;
0 commit comments