From c0b422245ef4e155977e7d0e0db8645ee6dde6d9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:35:03 -0300 Subject: [PATCH 1/6] refactor: split nginx.conf into http, https and common Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx/config/common.conf | 129 +++++++++++++++++++++++ .docker/nginx/config/http.conf | 6 ++ .docker/nginx/config/https.conf | 14 +++ .docker/nginx/nginx.conf | 173 +------------------------------ 4 files changed, 153 insertions(+), 169 deletions(-) create mode 100644 .docker/nginx/config/common.conf create mode 100644 .docker/nginx/config/http.conf create mode 100644 .docker/nginx/config/https.conf diff --git a/.docker/nginx/config/common.conf b/.docker/nginx/config/common.conf new file mode 100644 index 0000000..29d71a5 --- /dev/null +++ b/.docker/nginx/config/common.conf @@ -0,0 +1,129 @@ +# Common configuration for both HTTP and HTTPS + +# Add headers to serve security related headers +add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;" always; + +# set max upload size +client_max_body_size 20G; +fastcgi_buffers 64 4K; + +# Enable gzip but do not remove ETag headers +gzip on; +gzip_vary on; +gzip_comp_level 4; +gzip_min_length 256; +gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; +gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm 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; + +# The settings allows you to optimize the HTTP2 bandwidth +client_body_buffer_size 512k; + +# HTTP response headers borrowed from Nextcloud `.htaccess` +add_header Referrer-Policy "no-referrer" always; +add_header X-Content-Type-Options "nosniff" always; +add_header X-Download-Options "noopen" always; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Permitted-Cross-Domain-Policies "none" always; +add_header X-Robots-Tag "noindex,nofollow" always; +add_header X-XSS-Protection "1; mode=block" always; + +# Remove X-Powered-By, which is an information leak +fastcgi_hide_header X-Powered-By; + +# Path to the root of your installation +root /var/www/html; + +# Add .mjs as a file extension for javascript +include mime.types; +types { + application/javascript js mjs; +} + +# Specify how to handle directories +index index.php index.html /index.php$request_uri; + +# Rule borrowed from `.htaccess` to handle Microsoft DAV clients +location = / { + if ( $http_user_agent ~ ^DavClnt ) { + return 302 /remote.php/webdav/$is_args$args; + } +} + +location = /robots.txt { + allow all; + log_not_found off; + access_log off; +} + +# Make a regex exception for `/.well-known` so that clients can still +# access it despite the existence of the regex rule +location ^~ /.well-known { + # The rules in this block are an adaptation of the rules + # in `.htaccess` that concern `/.well-known`. + + location = /.well-known/carddav { return 301 /remote.php/dav/; } + location = /.well-known/caldav { return 301 /remote.php/dav/; } + + location /.well-known/acme-challenge { try_files $uri $uri/ =404; } + location /.well-known/pki-validation { try_files $uri $uri/ =404; } + + # Let Nextcloud's API for `/.well-known` URIs handle all other + # requests by passing them to the front-end controller. + return 301 /index.php$request_uri; +} + +# Rules borrowed from `.htaccess` to hide certain paths from clients +location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } +location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } + +# Ensure this block, which passes PHP files to the PHP process, is above the blocks +# which handle static assets (as seen below). If this block is not declared first, +# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` +# to the URI, resulting in a HTTP 500 error response. +location ~ \.php(?:$|/) { + # Required for legacy support + rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; + + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + + fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice + fastcgi_param front_controller_active true; # Enable pretty urls + fastcgi_pass php-handler; + + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + + fastcgi_max_temp_file_size 0; +} + +# Serve static files +location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { + try_files $uri /index.php$request_uri; + add_header Cache-Control "public, max-age=15778463"; + access_log off; # Optional: Don't log access to assets + + location ~ \.wasm$ { + default_type application/wasm; + } +} + +location ~ \.woff2?$ { + try_files $uri /index.php$request_uri; + expires 7d; # Cache-Control policy borrowed from `.htaccess` + access_log off; # Optional: Don't log access to assets +} + +# Rule borrowed from `.htaccess` +location /remote { + return 301 /remote.php$request_uri; +} + +location / { + try_files $uri $uri/ /index.php$request_uri; +} diff --git a/.docker/nginx/config/http.conf b/.docker/nginx/config/http.conf new file mode 100644 index 0000000..f7bd926 --- /dev/null +++ b/.docker/nginx/config/http.conf @@ -0,0 +1,6 @@ +server { + listen 80; + include /etc/nginx/conf.d/includes/*.conf; + + include /etc/nginx/conf.d/common.conf; +} diff --git a/.docker/nginx/config/https.conf b/.docker/nginx/config/https.conf new file mode 100644 index 0000000..f6e3e6c --- /dev/null +++ b/.docker/nginx/config/https.conf @@ -0,0 +1,14 @@ +server { + listen 443 ssl http2; + http2_max_field_size 16k; + http2_max_header_size 32k; + + ssl_certificate /tmp/nextcloud.pem; + ssl_certificate_key /tmp/nextcloud.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + include /etc/nginx/conf.d/includes/*.conf; + + include /etc/nginx/conf.d/common.conf; +} diff --git a/.docker/nginx/nginx.conf b/.docker/nginx/nginx.conf index 06168d7..5190c38 100644 --- a/.docker/nginx/nginx.conf +++ b/.docker/nginx/nginx.conf @@ -3,14 +3,12 @@ worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; - events { worker_connections 1024; } http { disable_symlinks off; - # Prevent nginx HTTP Server Detection server_tokens off; include /etc/nginx/mime.types; @@ -23,8 +21,6 @@ http { access_log /var/log/nginx/access.log main; sendfile on; - #tcp_nopush on; - keepalive_timeout 65; set_real_ip_from 10.0.0.0/8; @@ -32,172 +28,11 @@ http { set_real_ip_from 192.168.0.0/16; real_ip_header X-Real-IP; - #gzip on; - upstream php-handler { server nextcloud:9000; } - server { - listen 80; - include /etc/nginx/conf.d/includes/*.conf; - - # Add headers to serve security related headers - # Before enabling Strict-Transport-Security headers please read into this - # topic first. - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;" always; - - # set max upload size - client_max_body_size 20G; - fastcgi_buffers 64 4K; - - # Enable gzip but do not remove ETag headers - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; - gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm 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; - - # Pagespeed is not supported by Nextcloud, so if your server is built - # with the `ngx_pagespeed` module, uncomment this line to disable it. - #pagespeed off; - - # The settings allows you to optimize the HTTP2 bandwidth. - # See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/ - # for tuning hints - client_body_buffer_size 512k; - - # HSTS settings - # WARNING: Only add the preload option once you read about - # the consequences in https://hstspreload.org/. This option - # will add the domain to a hardcoded list that is shipped - # in all major browsers and getting removed from this list - # could take several months. - #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; - - # HTTP response headers borrowed from Nextcloud `.htaccess` - add_header Referrer-Policy "no-referrer" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "noindex,nofollow" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Remove X-Powered-By, which is an information leak - fastcgi_hide_header X-Powered-By; - - # Path to the root of your installation - root /var/www/html; - - # Add .mjs as a file extension for javascript - # Either include it in the default mime.types list - # or include you can include that list explicitly and add the file extension - # only for Nextcloud like below: - include mime.types; - types { - application/javascript js mjs; - } - - # Specify how to handle directories -- specifying `/index.php$request_uri` - # here as the fallback means that Nginx always exhibits the desired behaviour - # when a client requests a path that corresponds to a directory that exists - # on the server. In particular, if that directory contains an index.php file, - # that file is correctly served; if it doesn't, then the request is passed to - # the front-end controller. This consistent behaviour means that we don't need - # to specify custom rules for certain paths (e.g. images and other assets, - # `/updater`, `/ocs-provider`), and thus - # `try_files $uri $uri/ /index.php$request_uri` - # always provides the desired behaviour. - index index.php index.html /index.php$request_uri; - - # Rule borrowed from `.htaccess` to handle Microsoft DAV clients - location = / { - if ( $http_user_agent ~ ^DavClnt ) { - return 302 /remote.php/webdav/$is_args$args; - } - } - - location = /robots.txt { - allow all; - log_not_found off; - access_log off; - } - - # Make a regex exception for `/.well-known` so that clients can still - # access it despite the existence of the regex rule - # `location ~ /(\.|autotest|...)` which would otherwise handle requests - # for `/.well-known`. - location ^~ /.well-known { - # The rules in this block are an adaptation of the rules - # in `.htaccess` that concern `/.well-known`. - - location = /.well-known/carddav { return 301 http://$host/remote.php/dav/; } - location = /.well-known/caldav { return 301 http://$host/remote.php/dav/; } - - location /.well-known/acme-challenge { try_files $uri $uri/ =404; } - location /.well-known/pki-validation { try_files $uri $uri/ =404; } - - # Let Nextcloud's API for `/.well-known` URIs handle all other - # requests by passing them to the front-end controller. - return 301 /index.php$request_uri; - } - - # Rules borrowed from `.htaccess` to hide certain paths from clients - location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } - location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } - - # Ensure this block, which passes PHP files to the PHP process, is above the blocks - # which handle static assets (as seen below). If this block is not declared first, - # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` - # to the URI, resulting in a HTTP 500 error response. - location ~ \.php(?:$|/) { - # Required for legacy support - rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; - - fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; - set $path_info $fastcgi_path_info; - try_files $fastcgi_script_name =404; - - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $path_info; - - fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice - fastcgi_param front_controller_active true; # Enable pretty urls - fastcgi_pass php-handler; - - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - - fastcgi_max_temp_file_size 0; - } - - # Serve static files - location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { - try_files $uri /index.php$request_uri; - add_header Cache-Control "public, max-age=15778463"; - access_log off; # Optional: Don't log access to assets - - location ~ \.wasm$ { - default_type application/wasm; - } - } - - location ~ \.woff2?$ { - try_files $uri /index.php$request_uri; - expires 7d; # Cache-Control policy borrowed from `.htaccess` - access_log off; # Optional: Don't log access to assets - } - - # Rule borrowed from `.htaccess` - location /remote { - return 301 /remote.php$request_uri; - } - - location / { - try_files $uri $uri/ /index.php$request_uri; - } - } -} \ No newline at end of file + # Load HTTP and HTTPS server configurations + include /etc/nginx/conf.d/http.conf; + include /etc/nginx/conf.d/https.conf; +} From 1d9634b375234249ce58e305eb1e36926f40dd09 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:35:30 -0300 Subject: [PATCH 2/6] feat: add entrypoint to nginx Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-entrypoint.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .docker/scripts/nginx-entrypoint.sh diff --git a/.docker/scripts/nginx-entrypoint.sh b/.docker/scripts/nginx-entrypoint.sh new file mode 100644 index 0000000..cb43c89 --- /dev/null +++ b/.docker/scripts/nginx-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Generate self-signed certificate if it doesn't exist +if [ ! -f /tmp/nextcloud.pem ]; then + echo "Generating self-signed certificate..." + openssl req -new -x509 -days 365 -nodes \ + -subj "/C=BR/ST=State/L=City/O=Nextcloud/CN=localhost" \ + -out /tmp/nextcloud.pem -keyout /tmp/nextcloud.pem 2>/dev/null || true +fi + +# Execute nginx +exec nginx -g "daemon off;" From 7883fbde8300671a42c4ea30fc052eb30b086a8e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:40:32 -0300 Subject: [PATCH 3/6] chore: update entrypoint to setup https Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/Dockerfile.nginx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.docker/Dockerfile.nginx b/.docker/Dockerfile.nginx index 16181bb..e797acc 100644 --- a/.docker/Dockerfile.nginx +++ b/.docker/Dockerfile.nginx @@ -1,3 +1,11 @@ FROM nginx:alpine -COPY nginx/nginx.conf /etc/nginx/nginx.conf \ No newline at end of file +RUN apk add --no-cache openssl + +COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY nginx/config/ /etc/nginx/conf.d/ +COPY scripts/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh + +RUN chmod +x /usr/local/bin/nginx-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/nginx-entrypoint.sh"] \ No newline at end of file From e87ad2a719b0335aaf1836a93fb3f6dcbf01ba3b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:41:00 -0300 Subject: [PATCH 4/6] chore: bump php Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d3ca14a..ab64b95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,7 @@ services: image: ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION:-82}:latest # build: # context: .docker/ - # dockerfile: Dockerfile.php${PHP_VERSION:-81} + # dockerfile: Dockerfile.php${PHP_VERSION:-82} volumes: - ./.docker/scripts/wait-for-db.php:/var/www/scripts/wait-for-db.php - ~/.composer:/var/www/.composer/ From 0c3035ebc8ad7f8b1108592d959cfb2d0b1f855f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:42:05 -0300 Subject: [PATCH 5/6] chore: update nginx build instructions Make compatible with newest nginx Dockerfile Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ab64b95..67299f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,7 +62,9 @@ services: - host.docker.internal:host-gateway nginx: image: ghcr.io/librecodecoop/nextcloud-dev-nginx:latest - # build: .docker/nginx + # build: + # context: .docker + # dockerfile: Dockerfile.nginx restart: unless-stopped volumes: - ./volumes/nextcloud:/var/www/html:ro From abe46773f08c0433877da2faa88e1fa6881f170f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:42:51 -0300 Subject: [PATCH 6/6] chore: add port 443 Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 67299f0..015d7bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,6 +71,7 @@ services: - ./volumes/nginx/includes:/etc/nginx/conf.d/includes/ ports: - 127.0.0.1:${HTTP_PORT:-80}:80 + - 127.0.0.1:443:443 mailpit: image: axllent/mailpit ports: