# Exim Outbound Mail Limits WHM: Stop Spam Runs Fast

Source: https://srvscripts.com/guides/exim-outbound-mail-limits-whm/
Updated: 2026-10-03
Publisher: srvScripts (https://srvscripts.com/)

A single compromised WordPress site can send fifty thousand messages in an hour and put a shared server’s IP on every blocklist by lunchtime. cPanel ships the controls to prevent this, but the defaults are loose enough that a spam run gets well underway before anything stops it. The settings below are the ones we apply to every shared server: an hourly cap per domain, deferral rather than rejection, headers that identify the source of each message, and a block on the web server user sending mail directly.

In short: Set Max hourly emails per domain to 200 to 500 in WHM » Tweak Settings » Mail (whmapi1 set_tweaksetting key=maxemailsperhour value=300), cap the failed or deferred percentage at around 25%, and leave over-limit mail deferred rather than rejected.

**Short answer:** Set **Max hourly emails per domain** to 200 to 500 in WHM » Tweak Settings » Mail (`whmapi1 set_tweaksetting key=maxemailsperhour value=300`), cap the failed or deferred percentage at around 25%, and leave over-limit mail deferred rather than rejected. In Exim Configuration Manager turn on the X-Source and X-PHP-Script headers, prevent “nobody” from sending, and restrict outgoing SMTP to root, exim and mailman (or use `SMTP_BLOCK` in CSF). With those in place a spam run becomes a deferred queue and a notification instead of a blocklisting.

## Per-domain hourly limits

**WHM » Server Configuration » Tweak Settings » Mail** has the two settings that matter most:

- **Max hourly emails per domain**: the default is unlimited on some builds and 1,000 on others. Set it to a number that legitimate customers rarely reach; 200 to 500 an hour is typical for shared hosting. Bulk senders should be on a relay service or a dedicated plan.

- **Max percentage of failed or deferred messages a domain may send per hour**: set to around 25%. A domain whose recipients are mostly invalid is almost always sending to a scraped list.

From the CLI:

```
whmapi1 set_tweaksetting key=maxemailsperhour value=300
whmapi1 set_tweaksetting key=emailsperdaynotify value=1000
whmapi1 set_tweaksetting key=maxpercentagefailedemailsperhour value=25
```

Per-package and per-account overrides exist for the customers who genuinely need more: **WHM » Packages » Edit a Package » Maximum Hourly Email by Domain Relayed**, or per account with `whmapi1 modifyacct user=USER MAX_EMAIL_PER_HOUR=1000`. Use them for known senders rather than raising the server-wide value.

## Defer, do not reject

When a domain hits the limit, cPanel can either reject further messages from the sending script or queue them for delivery in the next hour. Deferral is the better choice on a shared server: a rejected order-confirmation message is lost and generates a ticket, while a deferred one arrives an hour late. For a spam run, the messages sit in the queue where you can inspect and purge them, and the [Exim mail queue report script](/scripts/exim-mail-queue-report/) shows the domain at the top of the list.

Check that deferral rather than discard is what happens, and that notifications reach you:

```
whmapi1 get_tweaksetting key=emailsperdaynotify
grep -i 'domain has exceeded' /var/log/exim_mainlog | tail
```

The `emailsperdaynotify` threshold sends a notification to the contact address when a domain passes it, which is often your first warning of a compromise.

## Identify the source with X-Source headers

Exim on cPanel can add headers that record which user, script and directory generated each message sent through PHP’s `mail()` or a local sendmail call. Enable them under **WHM » Service Configuration » Exim Configuration Manager » Basic Editor » Mail**:

- **Add the X-Source, X-Source-Args and X-Source-Dir headers to all messages** — on.

- **Add the X-PHP-Script header** — on, and confirm `mail.add_x_header = On` in each PHP version’s ini so PHP records the calling script.

With these on, the headers of any outbound message name the exact PHP file that sent it, which turns the hunt described in [finding the source of outgoing spam](/guides/find-source-of-outgoing-spam-cpanel/) into a one-line grep:

```
grep -h 'X-PHP-Script' /var/spool/exim/input/*-H 2>/dev/null | sort | uniq -c | sort -rn | head
```

A common pitfall is a customer complaining that the headers reveal their server path to recipients. That is true, and a reasonable trade on a shared host; on a single-customer server you can turn them off and rely on `exim_mainlog` instead.

## Stop the web server user sending directly

Two settings close the path that most PHP mailers and injected scripts use. Under **Exim Configuration Manager » Basic Editor » Security**:

- **Prevent “nobody” from sending mail** — on. With PHP-FPM or suPHP every site runs as its own user, so nothing legitimate sends as `nobody`; only misconfigured handlers and exploits do.

- **Restrict outgoing SMTP to root, exim, and mailman (FKA SMTP Tweak)** — on. This adds an iptables rule (or its nftables equivalent) so that only the mail server can open connections to port 25 on remote hosts. A script that bundles its own SMTP client to bypass Exim then fails. Note that the rule uses the kernel firewall directly and may need re-applying after CSF or firewalld restarts; the cPanel CSF fork has a `SMTP_BLOCK` setting that provides the same thing in a firewall-aware way, so enable one or the other, not both.

The Tweak Setting for the SMTP restriction is `smtpmailgidonly`; the cPanel-managed equivalent in the CSF fork is `SMTP_BLOCK = "1"` with `SMTP_ALLOWUSER` for any exceptions. Check both:

```
whmapi1 get_tweaksetting key=smtpmailgidonly
grep -E '^SMTP_BLOCK|^SMTP_ALLOWUSER' /etc/csf/csf.conf
```

## Authentication and rate limiting for SMTP logins

Compromised mailbox passwords are the other half of the problem, and the hourly cap covers them too, because authenticated SMTP counts against the sender’s domain. Add cPHulk brute-force protection with a low threshold for SMTP, covered in [locking down WHM](/guides/lock-down-whm-2fa-cphulk-api-tokens/), and the **Require RFC-compliant HELO** and **Reject SPF failures** options in the Basic Editor. If you relay outbound mail through a smarthost, the caps still apply locally before the relay, which protects the relay account’s own limits from a single runaway domain.

## Verify

Test the cap on a throwaway domain with a short loop and confirm the messages defer once the limit is reached:

```
for i in $(seq 1 320); do echo "test $i" | mail -s "cap test $i" -r test@throwaway.example postmaster@throwaway.example; done
exim -bpc
grep -c 'has exceeded the max emails per hour' /var/log/exim_mainlog
```

Then confirm the SMTP restriction from a shell as a normal user: `nc -zv gmail-smtp-in.l.google.com 25` should fail with a connection refused or timeout. Finally, send a message through a PHP script and inspect the headers for `X-PHP-Script` and `X-Source`. Once those three checks pass, the next spam run on the server becomes a deferred queue and a notification, and the [outgoing spam guide](/guides/find-source-of-outgoing-spam-cpanel/) is a five-minute job rather than an afternoon.

## Exim outbound mail limits WHM at a glance

**Official documentation:** [Exim documentation](https://www.exim.org/docs.html), [cPanel & WHM documentation](https://docs.cpanel.net/), [RFC 5321 (SMTP)](https://www.rfc-editor.org/rfc/rfc5321).

**Related guides:** [Configuring SPF, DKIM and the auto-generated DMARC record in cPanel DNS](https://srvscripts.com/guides/spf-dkim-dmarc-cpanel-dns/) · [Setting up Dovecot Sieve mail filters in Roundcube on cPanel](https://srvscripts.com/guides/dovecot-sieve-filters-roundcube-cpanel/) · [Whitelisting MailBaby in cPanel greylisting, CSF and SpamAssassin](https://srvscripts.com/guides/whitelist-mailbaby-cpanel/).

## Frequently asked questions

### Does the hourly email limit in WHM apply to authenticated SMTP as well as PHP mail()?

Yes. Every message a domain sends counts against its hourly cap whether it came from a script, a local sendmail call or an authenticated SMTP login, so a compromised mailbox password is capped just like an injected mailer.

### How long do deferred messages wait after a domain exceeds the limit?

They stay in the Exim queue and are retried in the next hour once the domain is under its limit again; a spam run sits there until you inspect and purge it, while legitimate mail arrives roughly an hour late.

### Can I raise the outbound limit for one customer without changing the server-wide value?

Yes. Set a higher figure on the package under Maximum Hourly Email by Domain Relayed, or per account with `whmapi1 modifyacct user=USER MAX_EMAIL_PER_HOUR=1000`; the server-wide value stays as the default for everyone else.
