mirror of
https://github.com/Mailu/Mailu.git
synced 2024-12-12 10:45:38 +02:00
6a0e881522
This new advanced setting to harden cipher configuration on port 25. Changing the default is strongly discouraged, please read the documentation before doing so.
394 lines
11 KiB
Nginx Configuration File
394 lines
11 KiB
Nginx Configuration File
# 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_WEBUI_ADDRESS }};
|
|
{% if WEBMAIL_ADDRESS %}
|
|
set $webmail {{ WEBMAIL_ADDRESS }};
|
|
{% endif %}
|
|
{% if WEBDAV_ADDRESS %}
|
|
set $webdav {{ WEBDAV_ADDRESS }};
|
|
{% 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:50m;
|
|
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:50m;
|
|
{% endif %}
|
|
|
|
# Advertise real capabilites 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;
|
|
}
|
|
|
|
# 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 == 'true' %}
|
|
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;
|
|
}
|
|
|
|
server {
|
|
listen 110;
|
|
listen [::]:110;
|
|
{% if TLS %}
|
|
starttls only;
|
|
{% endif %}
|
|
protocol pop3;
|
|
pop3_auth plain;
|
|
auth_http_header Auth-Port 110;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
server {
|
|
listen 995 ssl;
|
|
listen [::]:995 ssl;
|
|
protocol pop3;
|
|
pop3_auth plain;
|
|
auth_http_header Auth-Port 995;
|
|
}
|
|
{% endif %}
|
|
{% endif %}
|
|
}
|