How to block an IP range? "Screened IPs" not being blocked

Hi there, I’m trying to temporarily block an abusive /16 subnet that is slamming a Discourse forum with requests. I tried this…

…but I’m still seeing a huge number of new requests from the same range in /var/discourse/shared/standalone/log/var-log/nginx/access.log .

Since these are configured in Discourse, I’m pretty sure that the IPs are blocked by Discourse, not NGINX, so they’ll show up in the NGINX logs. It’s Discourse that’s blocking them, not nginx. If you want to block them so that they don’t show up in the NGINX logs you could block them with your firewall.

לייק 1

Thanks Jay, you’re right about how it would still show up in the Nginx logs if blocked at the application level. However, the blocking at the application level isn’t doing what I would expect either. I tried connecting via a VPN and then I added my own IP address to the “Screened IPs” list, but it still allowed me to navigate the forum. I think that it must be called “Screened IPs” and not “Blocked IPs” because maybe it only prevents those IPs from registering an account?

What I need is for the Discourse app to deny access to the requested pages for requests coming from those addresses, and especially for it to not render those pages, as all of those requests are pegging the CPU.

Does Discourse see that you’re coming from one of the banned IPs if you go look at the user record from /admin/users? (If you’re behind a proxy like cloudflare, then Discourse may be seeing your proxy IP and not the IP of the user.)

Yep, when I connect to the VPN and then look at the last IP of my user account in Discourse it shows the IP that my VPN gave me. However, when I open an incognito browser window and try to register a new account then it doesn’t allow it:

New registrations are not allowed from your IP address.

So it appears that “Screened IPs” are just for registration, not for completely disallowing access to the website.

And to further complicate things, ufw / nftables on the host server apparently doesn’t block things as expected inside Docker:

לייק 1

I’m not sure where we document this but it’s come up a couple times recently.

“Screened IPs” blocks registrations and logins from blocked IPs that match, but not existing sessions.

I’m not sure where (if) we document this.

2 לייקים

Thanks for confirming.

So what would be the easiest method to deny requests from an IP or range of IPs? I found this, but it appears to be the opposite of what I need (whitelist rather than a blacklist), and messing around with the Nginx config inside the container feels like a total hack job:

I think doing it with UFW or IPTABLES . That removes stops it before Discourse gets involved. I’m always vague terrified mucking with firewalls for fear that I’ll lock myself out, but if you target just port 443 you aren’t in any danger.

Digital Ocean has some hints: UFW Essentials: Common Firewall Rules and Commands for Linux Security | DigitalOcean. I’d just Google for examples.

Exactly my fear as well. But I did enable it on the host and yet it’s still not blocking the problematic IP range. Apparently it needs a convoluted set of rules to make it apply the deny rules to the Docker containers.

Oh. Yeah. That’s totally true. I ended up blocking access from my docker-based web servers to the docker-based postgres database (likely not one of your problems).

Here’s another idea: Geo Blocking plugin

Thanks, I was looking at that too, but it doesn’t appear to allow blocking numeric IP ranges, just entire countries?

I haven’t installed it lately, but I’m pretty sure that it does

(emphasis added)

But then I found this:

So I think that means you can put in whatever network you want there.

לייק 1
/var/discourse/launcher enter app
apt install nano
nano /etc/nginx/conf.d/discourse.conf

And in the server { block add:

## 2025-10-27
deny 12.34.0.0/16;

Then save and

nginx -t
service nginx reload

I don’t quite understand why I’m still seeing hits from 12.34.x.x in the Nginx access.log, but it does seem to be working because the CPU usage is now normal again. Incredibly convoluted process IMHO, but good enough for now to get the site back up off its knees.

Yes, but currently it will only take AS numbers and not CIDR notation like 1.2.3.0/24.

לייק 1

Did that work? I think you need to do

sv restart nginx

But if you didn’t get an error I’m wrong.

Nginx is still seeing those? Does it show that it’s serving them? Do you see those in the rails production.log?

Ah. Maybe I should have paid more attention to that wikipedia page about AS numbers.

I think they’re both old aliases that are now mapped to systemd commands, so systemctl restart nginx would be the most proper.
EDIT: It appears that systemctl doesn’t work inside Docker. Here are some explanations about the differences:

Yep, in the access.log they are still appearing like:

[28/Oct/2025:00:29:27 +0000] "myforum.com" 12.34.56.78 "GET /permalink/12345 HTTP/1.1" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" "-" 403 691 "-" - 0.000 "-" "-" "-" "-" "-" "-" "-"

They seem to be mainly hitting permalinks now (migrated from an old forum platform). Is there some kind of a loophole with permalinks that lets it get around the deny rule? I haven’t checked the production.log yet.

Overall I would say that the lack of a GUI to inspect the access logs and no app-level IP blocklist is quite a significant limitation of Discourse. It’s an infrequent occurrence, but when you do get hit by one of these bot/scraper/crawler attacks you just want to immediately identify the source and mitigate it, without mucking about in a bunch of config files and arcane commands, especially not levels deep inside a Docker container with all the weird abstraction that takes place there. The very old forum platform I migrated from showed a simple list of the users or IPs with the highest number of requests during an adjustable time window, and it could even be filtered by which users and/or IPs occupied the highest amount of CPU time. That way I could quickly identify the offending address or range, and then there was a point-n-click interface to add it to a blocklist, and it would throw a 404 for requests from those IPs.

There is no problem.

If you have a deny rule, things still get logged in nginx. And it works correctly, because there is a 403 there, which means the client was denied access.

These requests are not being forwarded to Discourse. If you want to know whether your blocking is working correctly, you should either

  • look in the Discourse production.log instead
  • look in the nginx logs but ignore any entry that has a 403 in the HTTP response code field.
2 לייקים