# Observations about spam signups

**URL:** <https://forum.ghost.org/t/observations-about-spam-signups/61475>\
**Category:** Using Ghost\
**Created:** [January 16, 2026, 11:02am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475 "2026-01-16T11:02:31Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [February 26, 2026, 9:22am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/21 "2026-02-26T09:22:15Z")

</div>

This observation matches with this: [Spam for Lead Generation?](https://forum.ghost.org/t/spam-for-lead-generation/62008)

Are you able to see the IP addresses of send-magic-link requests? Can you check if they are also from Tor network? Maybe attacker is now checking another mail list.

---

<div class="post-metadata">

**Author:** ![fieldnoise](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/fieldnoise/32/32905_2.png) [@fieldnoise](https://forum.ghost.org/u/fieldnoise)\
**Post date:** [February 26, 2026, 7:00pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/22 "2026-02-26T19:00:04Z")

</div>

Where could I find the IP addresses?

---

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [February 26, 2026, 10:38pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/23 "2026-02-26T22:38:12Z")

</div>

Ghost itself doesn’t collect access logs. You need to collect access logs on your setup, then filter it by the `/members/api/send-magic-link/` path.

---

<div class="post-metadata">

**Author:** ![curiositry](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/curiositry/32/1084_2.png) [@curiositry](https://forum.ghost.org/u/curiositry)\
**Post date:** [March 1, 2026, 8:08pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/24 "2026-03-01T20:08:06Z")

</div>

I now see that the pattern I mentioned was just the tip of the iceberg on a larger spam attack more similar to the ones I’ve experienced before.

The difference was that this time it was going through the front end, and all the traffic was from exist node IP addresses. So I had to blacklist all exit nodes from that endpoint with my Nginx reverse proxy.

I’m still getting failed spam to the unchanged magic link endpoint, coming from the clearnet, but it just 404s :)

Another thing I did, finally, which I recommend other self-hosters do also, is set up a webhook from my transactional SMTP provider (Postmark), which emails me (via ActivePieces) if there are bounces/complaints/etc. The spam attacks tend to generate bounces pretty early on, and this system already caught when my fix hadn’t been deployed correctly, and spam was still getting through.

As for the handful that confirmed, I’m unclear about them. Maybe a few real people clicked confirm? I wonder if the IP for the confirmation matches the IP that submitted the sign-up. Looking back, I see a similar pattern with multiple sign-ins on first subscribe, going back a bit, so if it is spam signups it’s been going on longer, at very low volume. I noticed some of those addresses clicked all the links in my newsletter right when it arrived, which seemed suspicious, but a lot of email security software does this. I’m not sure whether to delete these members or not.

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [March 28, 2026, 5:19pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/25 "2026-03-28T17:19:26Z")

</div>

Hi everyone,

I’m dealing with a sustained spam attack similar to what others have reported here, but with a frustrating twist: my Cloudflare WAF rules aren’t working at all.

**Current situation:**

Three self-hosted Ghost blogs (Infomaniak VPS via Coolify) are being hit by bots attacking `/members/api/send-magic-link`. The pattern matches what [@muratcorlu](https://forum.ghost.org/u/muratcorlu) described in the opening post: SMS gateway addresses (@txt.bellmobility.catx@txt.att.net.b@vtext.comllmobility.ca, @txt.att.net, @vtext.com), 95%+ email failures (timeouts), and catastrophic Mailgun reputation damage (delivery rates dropped from 100% to 1.5-3%).

**What’s NOT working:**

Following [@jannis](https://forum.ghost.org/u/jannis)’s recommendation (#18), I deployed Cloudflare WAF rules blocking Tor (country T1) on `/members/api/send-magic-link`. Result: **zero blocked events** in Cloudflare Analytics. The spam continues unabated.

After investigation, I discovered why: **the bots are bypassing Cloudflare entirely by attacking my server IP directly**. My Coolify setup exposes Ghost via Traefik on ports 80/443, making the server accessible from any IP. Even though my domains are proxied through Cloudflare (orange cloud), the bots simply hit the origin IP.

Mailgun logs confirm this: `"originating-ip"` shows my VPS IP (185.x.x.x), not the bot’s IP. Cloudflare sees zero traffic to the spam endpoint.

**Infrastructure questions:**

This raises a question about what [@Kevin](https://forum.ghost.org/u/Kevin) mentioned in [the other thread](https://forum.ghost.org/t/ghost-sign-up-and-spam/54583/15): “This is something better handled at the various network levels above your Ghost instance.”

For self-hosters behind Cloudflare, what does “network level” actually mean when bots can bypass Cloudflare? The two obvious solutions are:

1. **Server-level firewall restricting ports 80/443 to Cloudflare IPs only** (15+ firewall rules per VPS)

2. **Cloudflare Tunnel** (but this breaks my setup where only subdomains are on the VPS while canonical domains host other apps)

Is this infrastructure hardening standard practice for self-hosted Ghost? If so, why isn’t it documented in Coolify or Ghost security guides? Am I missing something obvious here?

**Proposal: Community-maintained blocklist**

Separately, I think we need a better long-term solution than each site owner manually updating their blocklist. Inspired by repos like [disposable-email-domains](https://github.com/disposable-email-domains/disposable-email-domains), what if we created a **community-maintained blocklist of SMS gateway domains on GitHub**?

Workflow:

- Community members submit PRs when they discover new spam domains

- Ghost users pull the updated list periodically (manually or via webhook/scheduled task)

- Everyone benefits from collective intelligence instead of playing whack-a-mole individually

This wouldn’t solve the infrastructure problem (bots bypassing Cloudflare), but it would make the blocklist approach more maintainable for everyone. Thoughts? Would anyone be interested in collaborating on this?

**Mailgun frustration:**

One final note: Mailgun sent **zero automated alerts** despite weeks of 95%+ failure rates and drastic reputation degradation. I only discovered this by manually checking the dashboard. For others dealing with this, I’d recommend setting up your own monitoring.

**Questions:**

1. For self-hosters behind Cloudflare: how are you preventing direct-to-origin attacks? Is firewall hardening or Cloudflare Tunnel the standard approach?

2. For Ghost(Pro) users: [@Kevin](https://forum.ghost.org/u/Kevin), could you share any details about how Ghost(Pro) handles this at the “network level” so self-hosters can learn from it?

3. Would anyone be interested in collaborating on a GitHub-based community blocklist for SMS gateway domains?

Thanks, Bastien

---

<div class="post-metadata">

**Author:** ![jannis](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jannis/32/39718_2.png) [@jannis](https://forum.ghost.org/u/jannis)\
**Post date:** [March 28, 2026, 5:54pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/26 "2026-03-28T17:54:25Z")

</div>

The spam attack we reported here was not the SMS gateway attack – that was rather something that already happened last year.

This year’s attack (I hope I don’t have to write this every year 🙃 ) was from normal email addresses.

> [@bastien](#):
>
> Mailgun logs confirm this: `"originating-ip"` shows my VPS IP (185.x.x.x), not the bot’s IP. Cloudflare sees zero traffic to the spam endpoint.

This is somewhat of a red herring. The communication to Mailgun should only come from your server and NOT from the visitors. So, I’d ignore that.

> [@bastien](#):
>
> My Coolify setup exposes Ghost via Traefik on ports 80/443, making the server accessible from any IP.

This is the bigger problem.

Ideally, you wouldn’t make that possible at all. The simple (but also circumventable) method would be to have an allow list of IPs in Traefik that only allow Cloudflare IPs: [IP Ranges | Cloudflare](https://www.cloudflare.com/ips/)

Circumventable because any Cloudflare worker would also have that IP. So, I could just write a CF worker to attack your site and get through.

The better way: [Cloudflare Tunnel · Cloudflare Docs](https://developers.cloudflare.com/tunnel/)

> [@bastien](#):
>
> For self-hosters behind Cloudflare: how are you preventing direct-to-origin attacks? Is firewall hardening or Cloudflare Tunnel the standard approach?

Yeah, see above. If you have Cloudflare tunnels, I’d just lock down the server completely (apart from SSH, but even that should be behind fail2ban and hardened further).

> [@bastien](#):
>
> For Ghost(Pro) users: [@Kevin](https://forum.ghost.org/u/Kevin), could you share any details about how Ghost(Pro) handles this at the “network level” so self-hosters can learn from it?

Not Ghost(Pro), but on Magic Pages all the best practices I shared here are applied. Firewalls (on Cloudflare – since no direct path to the servers exists) are always adapted to what I see in logs across all sites. Self-hosting also means that you monitor all these things yourself. Managed hosting _should_ take care of this for you. Running a server doesn’t mean you set the server (and network stack) up once. It’s constant work and monitoring.

> [@bastien](#):
>
> Would anyone be interested in collaborating on a GitHub-based community blocklist for SMS gateway domains?

There are quite a few floating around on the forum in other threads already – you could just start by sharing one of these on Github.

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [March 28, 2026, 6:18pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/27 "2026-03-28T18:18:39Z")

</div>

Thanks [@jannis](https://forum.ghost.org/u/jannis) for the detailed response!

**Re: Mailgun originating-ip** Got it, that makes sense—I was chasing a red herring there.

**Re: Infrastructure** You’re right that the Traefik exposure is the core problem. My hesitation with Cloudflare Tunnel comes from my specific setup:

- Canonical domains host apps **outside** the VPS (external hosting platforms)

- Only **subdomains** (e.g., [blog.example.com](http://blog.example.com)) are on the VPS

- Ghost blogs are only accessible via these subdomains

My concern: if I enable Cloudflare Tunnel for these domains, wouldn’t it route **all** traffic (including canonical domain requests) to the VPS, breaking the external apps?

Or can Cloudflare Tunnel be configured to route only specific subdomains while leaving the canonical domains unaffected? If so, I’d be happy to implement it—I just haven’t found documentation confirming this is possible.

**Re: IP allowlist in Traefik** I understand this is circumventable via CF Workers, but would it still be worth implementing as a first layer while I figure out the Tunnel configuration?

**Re: GitHub blocklist** I hear you that lists already exist scattered across forum threads, but I’m not sure a personal GitHub repo would have much authority or adoption. For it to be useful, it would need either:

1. To live in the official Ghost repo (though I understand Ghost team might not want that maintenance burden)

2. Ghost to support fetching a community-maintained list from a URL (e.g., `"spam.blocklist_url": "``https://ghost.org/community/sms-blocklist.txt``"`)

Otherwise, it’s just another random list competing with all the others already in forum posts. That said, if infrastructure hardening (Tunnel + firewall) solves the problem properly, the blocklist becomes less critical anyway.

**Re: Monitoring** Point taken on self-hosting = constant monitoring. This attack was a wake-up call that I need better alerting (which is partly why I’m also reaching out to Mailgun separately about their lack of proactive alerts).

Thanks again for the insights!

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [March 28, 2026, 6:21pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/28 "2026-03-28T18:21:04Z")

</div>

**Re: VPS-level firewall**  
Before going the Cloudflare Tunnel route, would it make sense to simply lock down ports 80/443 at the VPS firewall level to only accept Cloudflare IP ranges? This would be simpler to implement and wouldn’t risk breaking my external apps setup. I know it’s circumventable via CF Workers, but is it still worth doing as a practical defense layer?

---

<div class="post-metadata">

**Author:** ![jannis](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jannis/32/39718_2.png) [@jannis](https://forum.ghost.org/u/jannis)\
**Post date:** [March 28, 2026, 6:24pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/29 "2026-03-28T18:24:49Z")

</div>

> [@bastien](#):
>
> My concern: if I enable Cloudflare Tunnel for these domains, wouldn’t it route **all** traffic (including canonical domain requests) to the VPS, breaking the external apps?

No, you’re pretty flexible in how you configure these. I don’t use tunnels for the overall Magic Pages infrastructure (but also don’t want to go into too many details of the actual setup to keep attack vectors low), but have it set up for a Grafana dashboard, for example. That runs a specific subdomain of my domain onto a specific port.

> [@bastien](#):
>
> **Re: IP allowlist in Traefik** I understand this is circumventable via CF Workers, but would it still be worth implementing as a first layer while I figure out the Tunnel configuration?

Definitely a good first step (but anyone reading here will that also knows your IP will then know how to get in 🙃 – so I wouldn’t trust it as a fully secure setup).

> [@bastien](#):
>
> **Re: GitHub blocklist** I hear you that lists already exist scattered across forum threads, but I’m not sure a personal GitHub repo would have much authority or adoption. For it to be useful, it would need either:
> 
> 1. To live in the official Ghost repo (though I understand Ghost team might not want that maintenance burden)
> 2. Ghost to support fetching a community-maintained list from a URL (e.g., `"spam.blocklist_url": "``https://ghost.org/community/sms-blocklist.txt``"`)

I’d have to disagree a bit here. A list like that wouldn’t need authority – just somebody maintaining it. If somebody searches for it, they’ll find it. I don’t think the Ghost team would want the maintenance burden, as you pointed out.

I do see this as something the community should provide if it’s important enough.

> [@bastien](#):
>
> which is partly why I’m also reaching out to Mailgun separately about their lack of proactive alerts

Phew, I’d not hope for anything in that regard. They’ll likely point you towards their webhooks and that’s it. As much as I love them for reliable deliverability, they aren’t known for proactivness in that regard 😅

---

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [March 28, 2026, 9:29pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/30 "2026-03-28T21:29:22Z")

</div>

To prevent CDN bypassers, actually there is [an undocumented feature built-in to Ghost](https://github.com/TryGhost/Ghost/blob/d5fca1cd482bc163d56a4da7b24a38a583bcc7b5/ghost/core/core/app.js#L41).

If you set a key on “hostSettings.siteId” configuration value, then Ghost always expects this key on all requests with `x-site-id` request header. If you add this request header on your Cloudflare Rules, then direct requests to your Ghost instance bypassing Cloudflare will always fail.

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [March 29, 2026, 8:21am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/31 "2026-03-29T08:21:59Z")

</div>

Hi @muratcorlu,

Thanks for sharing the hostSettings.siteId approach — I’d like to implement it but I have one blocking question before I do.

The WAF rule only works if the request to /members/api/ carries the x-site-id header. My concern is: who actually sends that header? Does Ghost Portal read the siteId from the site config and automatically inject it into its fetch requests to /members/api/send-magic-link — or does this require additional configuration?

If Portal doesn’t inject the header natively, the WAF rule would block legitimate signups too, which is obviously not what we want.

Could you confirm the exact mechanism? Specifically:

1. Does Portal.js automatically include x-site-id on every /members/api/ request once hostSettings.siteId is set in config.production.json?
2. Is there anything else to configure on the Ghost or Cloudflare side?

Thanks in advance — this would be a very clean solution for self-hosters using Cloudflare.

---

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [March 29, 2026, 9:33am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/32 "2026-03-29T09:33:38Z")

</div>

This header is supposed to be used with CDN proxies. All of the traffic (including requests made by Portal.js) comes to your server through Cloudflare. The idea is, you will add this request header on Cloudflare, with a rule. More specifically " Request Header Transform Rule".

 ![Screenshot 2026-03-29 at 11.30.48](https://us1.discourse-cdn.com/flex015/uploads/ghost2/original/3X/3/7/37fc9df9100c434821268b1df5f6d3b8f8b0d1b2.png)

 ![Screenshot 2026-03-29 at 11.33.17](https://us1.discourse-cdn.com/flex015/uploads/ghost2/original/3X/1/9/19378ccb0156aa3c96bf9dc3fcbe8bf013dd5962.png)

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [March 29, 2026, 3:20pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/33 "2026-03-29T15:20:17Z")

</div>

It seems to be working… How come this miracle solution hasn’t been mentioned anywhere on the forum before?

---

<div class="post-metadata">

**Author:** ![timber](https://avatars.discourse-cdn.com/v4/letter/t/5daacb/32.png) [@timber](https://forum.ghost.org/u/timber)\
**Post date:** [April 14, 2026, 7:03am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/34 "2026-04-14T07:03:27Z")

</div>

How about this approach?

> [@Possible Spam Signup Remedy](https://forum.ghost.org/t/possible-spam-signup-remedy/62574):
>
> Hi all, I‘ve been following the various threads about how to prevent spammers from signing up random emails for whatever reasons. It happens to my super small self-hosted blog as well. Obviously, I don’t want people to receive unwanted signup emails and possibly declaring them as spam which can hurt my sender’s reputation. My idea was to set a custom header that nginx verifies when /members/api/send-magic-link/ is hit. So I added this code to Settings→Advanced→Code Injection→Header: \<script\> …

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [April 14, 2026, 8:20am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/35 "2026-04-14T08:20:25Z")

</div>

@timber The solution right above yours worked perfectly for me, but you have to use Cloudflare. Yours seems to work on the same principle, but it’s more manual, isn’t it?

---

<div class="post-metadata">

**Author:** ![timber](https://avatars.discourse-cdn.com/v4/letter/t/5daacb/32.png) [@timber](https://forum.ghost.org/u/timber)\
**Post date:** [April 14, 2026, 8:33am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/36 "2026-04-14T08:33:06Z")

</div>

Yes, it is a hands-on approach without Cloudflare. I tried Cloudflare a while back but the free plan didn’t solve the problem with spammers for me. My blog is too small to justify additional costs.

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [April 14, 2026, 10:46am UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/37 "2026-04-14T10:46:55Z")

</div>

@timber You don’t need to pay Cloudflare for that. The free plan is enough. I also run a small blog.

---

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [April 14, 2026, 9:02pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/38 "2026-04-14T21:02:23Z")

</div>

Since 2 days, Synaps Media sites are getting a new wave of spam signup requests. This time source IPs are not from Tor network. They are coming from very wide range of IP addresses, some from VPNs or servers, some looks like residential IPs.

Attacker apparently uses headless browser. It’s very difficult to distinguish the traffic from real ones. So, patching fetch api or changing endpoint url would not work, since it’s just navigating the site like a real user.

Current situation is very difficult to prevent, without an act on Ghost itself. 😕

---

<div class="post-metadata">

**Author:** ![bastien](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/bastien/32/40675_2.png) [@bastien](https://forum.ghost.org/u/bastien)\
**Post date:** [April 15, 2026, 12:20pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/39 "2026-04-15T12:20:31Z")

</div>

@muratcorlu I don’t get it—does your workaround with the header request in the Cloudflare rule no longer work? For me, it completely eliminated all the issues.

---

<div class="post-metadata">

**Author:** ![muratcorlu](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/muratcorlu/32/38509_2.png) [@muratcorlu](https://forum.ghost.org/u/muratcorlu)\
**Post date:** [April 15, 2026, 12:40pm UTC](https://forum.ghost.org/t/observations-about-spam-signups/61475/40 "2026-04-15T12:40:43Z")

</div>

I don’t use Cloudflare and I didn’t try that approach. Because I’m not talking about my personal site, all sites on Synaps Media instead. I’m hesitant to patch fetch API on all Ghost sites running on Synaps Media.

But my main point was, what I observed is, attacker seems like rendering the page on an headless browser (making requests to HTML, static files and other api requests, like a regular visitor). Which would mean it will run JS code of my patch as well.

Of course, there can be multiple approaches they are using.

[Previous page](https://forum.ghost.org/t/observations-about-spam-signups/61475.md?page=1)

[Next page](https://forum.ghost.org/t/observations-about-spam-signups/61475.md?page=3)
