feat(nginx): add rate limiting with per-domain overrides
- Global rate limit: 10 req/s with burst of 20 - Connection limit: 30 concurrent per IP - Per-domain override support (requests, burst, enable/disable) - SearXNG gets higher limits (20/40) to tolerate bot traffic - Returns 429 when rate limited
This commit is contained in:
parent
2bc375ab86
commit
790501d290
2 changed files with 65 additions and 2 deletions
|
|
@ -101,11 +101,19 @@
|
||||||
myModules.nginx = {
|
myModules.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
email = "info@ashisgreat.xyz";
|
email = "info@ashisgreat.xyz";
|
||||||
|
rateLimit = {
|
||||||
|
enable = true;
|
||||||
|
requests = 10;
|
||||||
|
burst = 20;
|
||||||
|
};
|
||||||
domains = {
|
domains = {
|
||||||
"search.ashisgreat.xyz" = {
|
"search.ashisgreat.xyz" = {
|
||||||
port = 8888;
|
port = 8888;
|
||||||
# SearXNG sets its own CSP in settings.yml — omit at Nginx level to avoid conflicts
|
# SearXNG sets its own CSP in settings.yml — omit at Nginx level to avoid conflicts
|
||||||
contentSecurityPolicy = null;
|
contentSecurityPolicy = null;
|
||||||
|
# Search engine — slightly more permissive for bot traffic
|
||||||
|
rateLimit.requests = 20;
|
||||||
|
rateLimit.burst = 40;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Nginx Reverse Proxy Module
|
# Nginx Reverse Proxy Module
|
||||||
# Provides: Nginx with automatic Let's Encrypt certificates and security headers
|
# Provides: Nginx with automatic Let's Encrypt certificates, security headers, and rate limiting
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# myModules.nginx = {
|
# myModules.nginx = {
|
||||||
|
|
@ -32,6 +32,28 @@ in
|
||||||
description = "Email address for Let's Encrypt registration";
|
description = "Email address for Let's Encrypt registration";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rateLimit = {
|
||||||
|
enable = lib.mkEnableOption "Nginx rate limiting";
|
||||||
|
|
||||||
|
zone = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "10m";
|
||||||
|
description = "Size of the shared memory zone for rate limiting";
|
||||||
|
};
|
||||||
|
|
||||||
|
requests = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 10;
|
||||||
|
description = "Number of requests allowed per second (burst applies on top)";
|
||||||
|
};
|
||||||
|
|
||||||
|
burst = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 20;
|
||||||
|
description = "Maximum burst of requests allowed beyond the rate";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
domains = lib.mkOption {
|
domains = lib.mkOption {
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -52,6 +74,26 @@ in
|
||||||
description = "Content-Security-Policy header value. Set to null to omit.";
|
description = "Content-Security-Policy header value. Set to null to omit.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rateLimit = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.bool;
|
||||||
|
default = null;
|
||||||
|
description = "Enable rate limiting for this vhost. Defaults to global rateLimit.enable.";
|
||||||
|
};
|
||||||
|
|
||||||
|
requests = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Requests per second for this vhost. Defaults to global rateLimit.requests.";
|
||||||
|
};
|
||||||
|
|
||||||
|
burst = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Burst size for this vhost. Defaults to global rateLimit.burst.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
extraLocations = lib.mkOption {
|
extraLocations = lib.mkOption {
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -98,6 +140,14 @@ in
|
||||||
recommendedProxySettings = true;
|
recommendedProxySettings = true;
|
||||||
recommendedTlsSettings = true;
|
recommendedTlsSettings = true;
|
||||||
|
|
||||||
|
# Rate limiting zones (one per domain for per-domain limits)
|
||||||
|
commonHttpConfig = lib.optionalString cfg.rateLimit.enable ''
|
||||||
|
# Global rate limiting zone
|
||||||
|
limit_req_zone $binary_remote_addr zone=global:10m rate=${toString cfg.rateLimit.requests}r/s;
|
||||||
|
# Limit connection flooding
|
||||||
|
limit_conn_zone $binary_remote_addr zone=connlimit:10m;
|
||||||
|
'';
|
||||||
|
|
||||||
virtualHosts = lib.mapAttrs' (domain: opts: {
|
virtualHosts = lib.mapAttrs' (domain: opts: {
|
||||||
name = domain;
|
name = domain;
|
||||||
value = {
|
value = {
|
||||||
|
|
@ -127,7 +177,12 @@ in
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
proxyPass = "http://127.0.0.1:${toString opts.port}";
|
proxyPass = "http://127.0.0.1:${toString opts.port}";
|
||||||
extraConfig = opts.extraConfig;
|
extraConfig = opts.extraConfig + lib.optionalString (if opts.rateLimit.enable != null then opts.rateLimit.enable else cfg.rateLimit.enable) ''
|
||||||
|
# Rate limiting
|
||||||
|
limit_req zone=global burst=${toString (if opts.rateLimit.burst != null then opts.rateLimit.burst else cfg.rateLimit.burst)} nodelay;
|
||||||
|
limit_conn connlimit 30;
|
||||||
|
limit_req_status 429;
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
} // lib.mapAttrs' (locPath: locOpts: {
|
} // lib.mapAttrs' (locPath: locOpts: {
|
||||||
name = locPath;
|
name = locPath;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue