OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      bd1634440c6a
DATE      2026-07-11
SUBJECT   forgejo: fix rate-limit and fail2ban for real client ip
FILES     1 CHANGED
HASH      bd1634440c6a
DATE      2026-07-11
SUBJECT   forgejo: fix rate-limit and fail2ban
          for real client ip
FILES     1 CHANGED
 

diff --git a/modules/nixos/server/services/forgejo/default.nix b/modules/nixos/ser
ver/services/forgejo/default.nix
index fffffb3..a22bed1 100644
--- a/modules/nixos/server/services/forgejo/default.nix
+++ b/modules/nixos/server/services/forgejo/default.nix
@@ -97,11 +97,14 @@ in {
             Referrer-Policy "no-referrer"
           }
 
-          # rate limiting: 30 requests per minute per IP
+          # rate limiting: 300 req/min per real client. {client_ip} resolves
+          # the true client via trusted_proxies, not the shared cloudflare
+          # edge IP that {remote_host} gave (which made one user's page load
+          # of assets 429 everyone behind that edge)
           rate_limit {
             zone git {
-              key {remote_host}
-              events 30
+              key {client_ip}
+              events 300
               window 1m
             }
           }
@@ -109,8 +112,8 @@ in {
           # Handle proxying
           handle_path /* {
             reverse_proxy localhost:3000 {
-              header_up X-Real-IP {remote_host}
-              header_up X-Forwarded-For {remote_host}
+              header_up X-Real-IP {client_ip}
+              header_up X-Forwarded-For {client_ip}
               header_up X-Forwarded-Proto {scheme}
             }
           }
@@ -128,8 +131,11 @@ in {
     };
     environment = {
       etc."fail2ban/filter.d/forgejo.conf".text = mkIf fail2ban.enable (settingsF
ormat {
-        Definitions = {
-          failregex = "^.*(Failed authentication attempt|invalid credentials|Atte
mpted access of unknown user).";
+        # fail2ban only reads [Definition] (singular); and the regex needs
+        # <HOST> to extract the IP to ban. forgejo now logs the real client
+        # IP (X-Real-IP {client_ip}) as "... from <ip>:<port>".
+        Definition = {
+          failregex = "^.*(Failed authentication attempt|invalid credentials|Atte
mpted access of unknown user).* from <HOST>";
           journalmatch = "_SYSTEMD_UNIT=forgejo.service";
         };
       });

diff --git a/modules/nixos/server/services/f
orgejo/default.nix b/modules/nixos/server/se
rvices/forgejo/default.nix
index fffffb3..a22bed1 100644
--- a/modules/nixos/server/services/forgejo/
default.nix
+++ b/modules/nixos/server/services/forgejo/
default.nix
@@ -97,11 +97,14 @@ in {
             Referrer-Policy "no-referrer"
           }
 
-          # rate limiting: 30 requests per 
minute per IP
+          # rate limiting: 300 req/min per 
real client. {client_ip} resolves
+          # the true client via trusted_pro
xies, not the shared cloudflare
+          # edge IP that {remote_host} gave
 (which made one user's page load
+          # of assets 429 everyone behind t
hat edge)
           rate_limit {
             zone git {
-              key {remote_host}
-              events 30
+              key {client_ip}
+              events 300
               window 1m
             }
           }
@@ -109,8 +112,8 @@ in {
           # Handle proxying
           handle_path /* {
             reverse_proxy localhost:3000 {
-              header_up X-Real-IP {remote_h
ost}
-              header_up X-Forwarded-For {re
mote_host}
+              header_up X-Real-IP {client_i
p}
+              header_up X-Forwarded-For {cl
ient_ip}
               header_up X-Forwarded-Proto {
scheme}
             }
           }
@@ -128,8 +131,11 @@ in {
     };
     environment = {
       etc."fail2ban/filter.d/forgejo.conf".
text = mkIf fail2ban.enable (settingsFormat 
{
-        Definitions = {
-          failregex = "^.*(Failed authentic
ation attempt|invalid credentials|Attempted 
access of unknown user).";
+        # fail2ban only reads [Definition] 
(singular); and the regex needs
+        # <HOST> to extract the IP to ban. 
forgejo now logs the real client
+        # IP (X-Real-IP {client_ip}) as "..
. from <ip>:<port>".
+        Definition = {
+          failregex = "^.*(Failed authentic
ation attempt|invalid credentials|Attempted 
access of unknown user).* from <HOST>";
           journalmatch = "_SYSTEMD_UNIT=for
gejo.service";
         };
       });
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET