{
flake.modules.nixos.nginx =
{
config,
...
}:
let
inherit (config.networking) domain;
inherit (config.myLib) mkConst;
in
{
options.services.nginx.sslTemplate = mkConst {
forceSSL = true;
quic = true;
useACMEHost = domain;
};
options.services.nginx.goatCounterTemplate =
# nginx
mkConst ''
proxy_set_header Accept-Encoding "";
sub_filter "</head>" '<script data-goatcounter="https://analytics.${domain}/count" async src="https://analytics.${domain}/count.js"></script></head>';
sub_filter_last_modified on;
sub_filter_once on;
'';
options.services.nginx.headers =
# nginx
mkConst ''
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin $allow_origin always;
${config.services.nginx.headersNoAccessControlOrigin}
'';
options.services.nginx.headersNoAccessControlOrigin =
# nginx
mkConst ''
proxy_hide_header Access-Control-Allow-Methods;
add_header Access-Control-Allow-Methods $allow_methods always;
proxy_hide_header Strict-Transport-Security;
add_header Strict-Transport-Security $hsts_header always;
proxy_hide_header Content-Security-Policy;
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain}; object-src 'self' ${domain} *.${domain}; img-src 'self' data: https:; base-uri 'self'; frame-ancestors 'self';" always;
proxy_hide_header Referrer-Policy;
add_header Referrer-Policy no-referrer always;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options DENY always;
proxy_hide_header X-Content-Type-Options;
add_header X-Content-Type-Options nosniff always;
proxy_hide_header X-XSS-Protection;
add_header X-XSS-Protection "1; mode=block" always;
proxy_hide_header Permissions-Policy;
add_header Permissions-Policy "camera=(), geolocation=(), payment=(), usb=()" always;
'';
config.networking.firewall = {
allowedTCPPorts = [
443
80
];
allowedUDPPorts = [ 443 ];
};
config.services.prometheus.exporters.nginx = {
enable = true;
listenAddress = "[::]";
};
config.security.acme.users = [ "nginx" ];
config.services.nginx = {
enable = true;
statusPage = true;
recommendedBrotliSettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
commonHttpConfig = # nginx
''
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
# Cache only successful responses.
map $status $cache_header {
200 "public";
302 "public";
default "no-cache";
}
map $http_origin $allow_origin {
~^https://(?:.+\.)?${domain}$ $http_origin;
~^https://dr-radka\.pl$ $http_origin;
~^https://awesome-technologies\.github\.io$ $http_origin;
~^https://app\.element\.io$ $http_origin;
}
map $http_origin $allow_methods {
~^https://(?:.+\.)?${domain}$ "CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE";
~^https://dr-radka\.pl$ "CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE";
~^https://awesome-technologies\.github\.io$ "CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE";
~^https://app\.element\.io$ "DELETE, GET, OPTIONS, POST, PUT";
}
${config.services.nginx.headers}
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
'';
};
};
}