Follow-up to #35 (matrix #22). Two rate-limit problems on chat.lotusguild.org; testing the new decorations host exposed both.
1. Everyone shared one rate-limit bucket (existing bug). All requests reach LXC 106 from Nginx Proxy Manager (10.10.10.27; it's the only address in the access log), and there was no realip config. So chat_limit (20 r/s, burst 60, keyed by $binary_remote_addr) applied to the whole community at once: when a scanner probed /admin, /login, … everyone got 503s together. Fix: set_real_ip_from 10.10.10.27; real_ip_header X-Real-IP; in both server blocks. Only the proxy is trusted; NPM sets X-Real-IP to the client address, overwriting anything the client sent.
2. The decoration picker would trip it. Opening the profile decoration picker can fetch all 629 thumbnails; a full run against production returned 500 × 503. Before, they came from Nextcloud, a different host. /decorations/ now has its own zone (100 r/s, burst 700) instead of chat_limit.
Tested in a local nginx harness (prod rate, realip pointed at the harness):
Picker (629 thumbnails, 16 at a time): 629 × 200; the app loads right after, for the same user.
A scanner flooding 150 requests is throttled (62 × 200, 88 × 503), while another user during the flood gets 200.
The log shows each client's own address; with the real proxy address trusted, a spoofed X-Real-IP from elsewhere is ignored.
nginx -t is OK. The deploy runs nginx -t with rollback.
After the deploy I'll check that the production log shows real client addresses.
Follow-up to #35 (matrix #22). Two rate-limit problems on chat.lotusguild.org; testing the new decorations host exposed both.
**1. Everyone shared one rate-limit bucket (existing bug).** All requests reach LXC 106 from Nginx Proxy Manager (`10.10.10.27`; it's the only address in the access log), and there was no `realip` config. So `chat_limit` (20 r/s, burst 60, keyed by `$binary_remote_addr`) applied to **the whole community at once**: when a scanner probed `/admin`, `/login`, … everyone got 503s together. Fix: `set_real_ip_from 10.10.10.27; real_ip_header X-Real-IP;` in both server blocks. Only the proxy is trusted; NPM sets `X-Real-IP` to the client address, overwriting anything the client sent.
**2. The decoration picker would trip it.** Opening the profile decoration picker can fetch all 629 thumbnails; a full run against production returned **500 × 503**. Before, they came from Nextcloud, a different host. `/decorations/` now has its own zone (100 r/s, burst 700) instead of `chat_limit`.
**Tested in a local nginx harness** (prod rate, realip pointed at the harness):
- Picker (629 thumbnails, 16 at a time): **629 × 200**; the app loads right after, for the same user.
- A scanner flooding 150 requests is throttled (62 × 200, 88 × 503), while **another user during the flood gets 200**.
- The log shows each client's own address; with the real proxy address trusted, a spoofed `X-Real-IP` from elsewhere is ignored.
- `nginx -t` is OK. The deploy runs `nginx -t` with rollback.
After the deploy I'll check that the production log shows real client addresses.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
Every request reaches LXC 106 from Nginx Proxy Manager (10.10.10.27), and
nginx had no realip config, so chat_limit (20 r/s, burst 60, keyed by
$binary_remote_addr) was ONE bucket for the whole community: a scanner
burst 503ed real users. Trust X-Real-IP from the proxy only, in both
server blocks.
Decorations (#22) now come from this host, and the profile picker can
fetch all 629 thumbnails: ~500 of them 503ed in testing. /decorations/
gets its own zone (100 r/s, burst 700) instead of chat_limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared
merged commit f7ce35c636 into main2026-10-19 12:44:26 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up to #35 (matrix #22). Two rate-limit problems on chat.lotusguild.org; testing the new decorations host exposed both.
1. Everyone shared one rate-limit bucket (existing bug). All requests reach LXC 106 from Nginx Proxy Manager (
10.10.10.27; it's the only address in the access log), and there was norealipconfig. Sochat_limit(20 r/s, burst 60, keyed by$binary_remote_addr) applied to the whole community at once: when a scanner probed/admin,/login, … everyone got 503s together. Fix:set_real_ip_from 10.10.10.27; real_ip_header X-Real-IP;in both server blocks. Only the proxy is trusted; NPM setsX-Real-IPto the client address, overwriting anything the client sent.2. The decoration picker would trip it. Opening the profile decoration picker can fetch all 629 thumbnails; a full run against production returned 500 × 503. Before, they came from Nextcloud, a different host.
/decorations/now has its own zone (100 r/s, burst 700) instead ofchat_limit.Tested in a local nginx harness (prod rate, realip pointed at the harness):
X-Real-IPfrom elsewhere is ignored.nginx -tis OK. The deploy runsnginx -twith rollback.After the deploy I'll check that the production log shows real client addresses.
🤖 Generated with Claude Code
https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5