# Basic configuration user nginx; worker_processes auto; error_log /dev/stderr notice; pid /var/run/nginx.pid; load_module "modules/ngx_mail_module.so"; events { worker_connections 1024; } http { # Standard HTTP configuration with slight hardening include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server_tokens off; absolute_redirect off; resolver {{ RESOLVER }} valid=30s; {% if REAL_IP_HEADER %} real_ip_header {{ REAL_IP_HEADER }}; {% endif %} {% if REAL_IP_FROM %}{% for from_ip in REAL_IP_FROM.split(',') %} set_real_ip_from {{ from_ip }}; {% endfor %}{% endif %} # Header maps map $http_x_forwarded_proto $proxy_x_forwarded_proto { default $http_x_forwarded_proto; '' $scheme; } map $uri $expires { default off; ~*\.(ico|css|js|gif|jpeg|jpg|png|woff2?|ttf|otf|svg|tiff|eot|webp)$ 97d; } map $request_uri $loggable { /health 0; /auth/email 0; default 1; } access_log /dev/stdout combined if=$loggable; # compression gzip on; gzip_static on; gzip_types text/plain text/css application/xml application/javascript gzip_min_length 1024; # TODO: figure out how to server pre-compressed assets from admin container {% if KUBERNETES_INGRESS != 'true' and TLS_FLAVOR in [ 'letsencrypt', 'cert' ] %} # Enable the proxy for certbot if the flavor is letsencrypt and not on kubernetes # server { # Listen over HTTP listen 80; listen [::]:80; {% if TLS_FLAVOR == 'letsencrypt' %} location ^~ /.well-known/acme-challenge/ { proxy_pass http://127.0.0.1:8008; } {% endif %} # redirect to https location / { return 301 https://$host$request_uri; } } {% endif %} # Main HTTP server server { # Favicon stuff root /static; # Variables for proxifying set $admin {{ ADMIN_ADDRESS }}; set $antispam {{ ANTISPAM_ADDRESS }}:11334; {% if WEBMAIL_ADDRESS %} set $webmail {{ WEBMAIL_ADDRESS }}; {% endif %} {% if WEBDAV_ADDRESS %} set $webdav {{ WEBDAV_ADDRESS }}:5232; {% endif %} client_max_body_size {{ MESSAGE_SIZE_LIMIT|int + 8388608 }}; # Listen on HTTP only in kubernetes or behind reverse proxy {% if KUBERNETES_INGRESS == 'true' or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %} listen 80; listen [::]:80; {% endif %} # Only enable HTTPS if TLS is enabled with no error and not on kubernetes {% if KUBERNETES_INGRESS != 'true' and TLS and not TLS_ERROR %} listen 443 ssl http2; listen [::]:443 ssl http2; include /etc/nginx/tls.conf; ssl_stapling on; ssl_stapling_verify on; ssl_session_cache shared:SSLHTTP:3m; add_header Strict-Transport-Security 'max-age=31536000'; {% if not TLS_FLAVOR in [ 'mail', 'mail-letsencrypt' ] %} if ($proxy_x_forwarded_proto = http) { return 301 https://$host$request_uri; } {% endif %} {% endif %} # Remove headers to prevent duplication and information disclosure proxy_hide_header X-XSS-Protection; proxy_hide_header X-Powered-By; add_header X-Frame-Options 'SAMEORIGIN'; add_header X-Content-Type-Options 'nosniff'; add_header X-Permitted-Cross-Domain-Policies 'none'; add_header Referrer-Policy 'same-origin'; # mozilla autoconfiguration location ~ ^/(\.well\-known/autoconfig/)?mail/config\-v1\.1\.xml { rewrite ^ /internal/autoconfig/mozilla break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; } # microsoft autoconfiguration location ~* ^/Autodiscover/Autodiscover.json { rewrite ^ /internal/autoconfig/microsoft.json break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; } location ~* ^/Autodiscover/Autodiscover.xml { rewrite ^ /internal/autoconfig/microsoft break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; } # apple mobileconfig location ~ ^/(apple\.)?mobileconfig { rewrite ^ /internal/autoconfig/apple break; include /etc/nginx/proxy.conf; proxy_pass http://$admin; } {% if TLS_FLAVOR == 'mail-letsencrypt' %} location ^~ /.well-known/acme-challenge/ { proxy_pass http://127.0.0.1:8008; } {% endif %} # If TLS is failing, prevent access to anything except certbot {% if KUBERNETES_INGRESS != 'true' and TLS_ERROR and not (TLS_FLAVOR in [ 'mail-letsencrypt', 'mail' ]) %} location / { return 403; } {% else %} include /overrides/*.conf; # Actual logic {% if ADMIN == 'true' or WEBMAIL != 'none' %} location ~ ^/(sso|static)/ { include /etc/nginx/proxy.conf; proxy_pass http://$admin; } {% endif %} {% if WEB_WEBMAIL != '/' and WEBROOT_REDIRECT != 'none' %} location / { expires $expires; {% if WEBROOT_REDIRECT %} try_files $uri {{ WEBROOT_REDIRECT }}; {% else %} try_files $uri =404; {% endif %} } {% endif %} {% if WEBMAIL != 'none' %} location {{ WEB_WEBMAIL }} { {% if WEB_WEBMAIL != '/' %} rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break; {% endif %} include /etc/nginx/proxy.conf; auth_request /internal/auth/user; error_page 403 @webmail_login; proxy_pass http://$webmail; } {% if WEB_WEBMAIL == '/' %} location /sso.php { {% else %} location {{ WEB_WEBMAIL }}/sso.php { {% endif %} {% if WEB_WEBMAIL != '/' %} rewrite ^({{ WEB_WEBMAIL }})$ $1/ permanent; rewrite ^{{ WEB_WEBMAIL }}/(.*) /$1 break; {% endif %} include /etc/nginx/proxy.conf; auth_request /internal/auth/user; auth_request_set $user $upstream_http_x_user; auth_request_set $token $upstream_http_x_user_token; proxy_set_header X-Remote-User $user; proxy_set_header X-Remote-User-Token $token; error_page 403 @webmail_login; proxy_pass http://$webmail; } location @webmail_login { return 302 /sso/login; } {% endif %} {% if ADMIN == 'true' %} location {{ WEB_ADMIN }} { include /etc/nginx/proxy.conf; proxy_pass http://$admin; expires $expires; } location {{ WEB_ADMIN }}/antispam { rewrite ^{{ WEB_ADMIN }}/antispam/(.*) /$1 break; auth_request /internal/auth/admin; proxy_set_header X-Real-IP ""; proxy_set_header X-Forwarded-For ""; proxy_pass http://$antispam; } {% endif %} {% if WEBDAV != 'none' %} location /webdav { rewrite ^/webdav/(.*) /$1 break; auth_request /internal/auth/basic; auth_request_set $user $upstream_http_x_user; include /etc/nginx/proxy.conf; proxy_set_header X-Remote-User $user; proxy_set_header X-Script-Name /webdav; proxy_pass http://$webdav; } location ~ ^/.well-known/(carddav|caldav) { return 301 /webdav/; } {% endif %} {% endif %} location /internal { internal; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_pass_header Authorization; proxy_pass http://$admin; proxy_pass_request_body off; proxy_set_header Content-Length ""; } location /health { return 204; } } # Forwarding authentication server server { # Variables for proxifying set $admin {{ ADMIN_ADDRESS }}; listen 127.0.0.1:8000; location / { proxy_pass http://$admin/internal$request_uri; } } include /etc/nginx/conf.d/*.conf; } mail { server_name {{ HOSTNAMES.split(",")[0] }}; auth_http http://127.0.0.1:8000/auth/email; proxy_pass_error_message on; resolver {{ RESOLVER }} valid=30s; error_log /dev/stderr info; {% if TLS and not TLS_ERROR %} include /etc/nginx/tls.conf; ssl_session_cache shared:SSLMAIL:3m; {% endif %} # Advertise real capabilities of backends (postfix/dovecot) smtp_capabilities PIPELINING "SIZE {{ MESSAGE_SIZE_LIMIT }}" ETRN ENHANCEDSTATUSCODES 8BITMIME DSN; pop3_capabilities TOP UIDL RESP-CODES PIPELINING AUTH-RESP-CODE USER; imap_capabilities IMAP4 IMAP4rev1 UIDPLUS SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+; # Default SMTP server for the webmail (no encryption, but authentication) server { listen 10025; protocol smtp; smtp_auth plain; auth_http_header Auth-Port 10025; } # Default IMAP server for the webmail (no encryption, but authentication) server { listen 10143; protocol imap; smtp_auth plain; auth_http_header Auth-Port 10143; # ensure we talk HAPROXY protocol to the backends proxy_protocol on; } # SMTP is always enabled, to avoid losing emails when TLS is failing server { listen 25; listen [::]:25; {% if TLS and not TLS_ERROR %} {% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %} ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem; ssl_certificate /certs/letsencrypt/live/mailu-ecdsa/fullchain.pem; {% endif %} {% if TLS_PERMISSIVE %} ssl_protocols TLSv1 TLSv1.1 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA; ssl_prefer_server_ciphers on; {% endif %} starttls on; {% endif %} protocol smtp; smtp_auth none; auth_http_header Auth-Port 25; } # All other protocols are disabled if TLS is failing {% if not TLS_ERROR %} server { listen 143; listen [::]:143; {% if TLS %} starttls only; {% endif %} protocol imap; imap_auth plain; auth_http_header Auth-Port 143; # ensure we talk HAPROXY protocol to the backends proxy_protocol on; } server { listen 110; listen [::]:110; {% if TLS %} starttls only; {% endif %} protocol pop3; pop3_auth plain; auth_http_header Auth-Port 110; # ensure we talk HAPROXY protocol to the backends proxy_protocol on; } server { listen 587; listen [::]:587; {% if TLS %} starttls only; {% endif %} protocol smtp; smtp_auth plain login; auth_http_header Auth-Port 587; } {% if TLS %} server { listen 465 ssl; listen [::]:465 ssl; protocol smtp; smtp_auth plain login; auth_http_header Auth-Port 465; } server { listen 993 ssl; listen [::]:993 ssl; protocol imap; imap_auth plain; auth_http_header Auth-Port 993; # ensure we talk HAPROXY protocol to the backends proxy_protocol on; } server { listen 995 ssl; listen [::]:995 ssl; protocol pop3; pop3_auth plain; auth_http_header Auth-Port 995; # ensure we talk HAPROXY protocol to the backends proxy_protocol on; } {% endif %} {% endif %} }