cbpolicyd (also known as PolicyD or Cluebringer) is a policy delegation daemon for Postfix mail servers. It gives you fine-grained control over mail flow through features like rate limiting, greylisting, SPF checks, access control, and quota enforcement. If you run Postfix directly or as part of a Zimbra mail server stack, cbpolicyd is one of the best tools for fighting spam and abuse at the SMTP level – before messages ever hit your content filters.

In this guide, I will walk you through installing and configuring cbpolicyd on RHEL 10, Rocky Linux 10, and AlmaLinux 10. We will cover installation from source (since EPEL packages for cbpolicyd are not always current), database backend setup with SQLite or MySQL/MariaDB, Postfix integration, rate limiting policies, and the optional web management interface.

What cbpolicyd Does

cbpolicyd sits between Postfix and your mail policies. When Postfix receives a connection or message, it asks cbpolicyd whether to accept, reject, or defer the mail based on rules you define. Key features include:

  • Rate Limiting – Restrict how many messages a sender or domain can send per hour/day. Essential for stopping compromised accounts from spewing spam.
  • Greylisting – Temporarily reject mail from unknown senders. Legitimate servers retry; most spambots do not.
  • SPF Checks – Verify that the sending server is authorized to send mail for the claimed domain.
  • Access Control – Allow or deny mail based on sender, recipient, client IP, or HELO name.
  • Quotas – Enforce message count and size quotas per sender, recipient, or domain.

Prerequisites

Before starting, make sure you have the following in place:

  • A running RHEL 10, Rocky Linux 10, or AlmaLinux 10 server with root or sudo access
  • Postfix installed and working (standalone or as part of Zimbra)
  • A database backend – SQLite (simpler, good for low-traffic servers) or MariaDB/MySQL (better for production)
  • Basic familiarity with Postfix configuration
  • Firewall configured to allow SMTP traffic (ports 25, 465, 587)

Step 1 – Update the System

Start by making sure your system is fully updated:

sudo dnf update -y

Verify your OS release:

cat /etc/redhat-release

You should see output similar to:

Rocky Linux release 10.0 (Pelican)
# or
AlmaLinux release 10.0 (Purple Lion)
# or
Red Hat Enterprise Linux release 10.0 (Coughlan)

Step 2 – Install Required Dependencies

cbpolicyd is written in Perl. You need several Perl modules and build tools. Install them now:

sudo dnf install -y perl perl-DBI perl-DBD-SQLite perl-DBD-MySQL \
  perl-Net-Server perl-Net-CIDR perl-Config-IniFiles perl-Cache-FastMmap \
  perl-Mail-SPF perl-Net-DNS perl-IO-Multiplex \
  gcc make wget tar

If you plan to use MariaDB as the backend, install it as well:

sudo dnf install -y mariadb-server mariadb

Verify Perl is installed correctly:

perl -v | head -2

Step 3 – Install cbpolicyd from Source

The EPEL repository may carry an older version of cbpolicyd or may not have it packaged for RHEL 10 yet. Building from source gives you the latest stable release and full control over the installation.

Download the latest cbpolicyd source (version 2.1.x as of this writing):

cd /usr/local/src
sudo wget https://download.policyd.org/v2.1.x-201505/cluebringer-v2.1.x-201505.tar.xz

Extract the archive:

sudo tar xf cluebringer-v2.1.x-201505.tar.xz
cd cluebringer-v2.1.x-201505

Create the cbpolicyd system user:

sudo useradd -r -s /sbin/nologin -d /etc/cluebringer cbpolicyd

Copy the cbpolicyd files to their proper locations:

# Create directories
sudo mkdir -p /etc/cluebringer
sudo mkdir -p /usr/lib/cluebringer
sudo mkdir -p /usr/share/doc/cluebringer

# Copy files
sudo cp -r cbp /usr/lib/cluebringer/
sudo cp cbpadmin /usr/local/bin/
sudo cp cbpolicyd /usr/local/sbin/

# Copy configuration
sudo cp cluebringer.conf /etc/cluebringer/cluebringer.conf

# Copy database schemas
sudo cp -r database /usr/share/doc/cluebringer/

# Set permissions
sudo chown -R cbpolicyd:cbpolicyd /etc/cluebringer
sudo chmod 640 /etc/cluebringer/cluebringer.conf

Verify the binary is in place:

ls -la /usr/local/sbin/cbpolicyd

Step 4 – Configure the Database Backend

cbpolicyd needs a database to store policies, quotas, and greylisting data. You have two options – pick the one that fits your setup.

Option A – SQLite (Simple Setup)

SQLite works well for single-server setups with moderate traffic. No separate database service to manage.

# Create the database directory
sudo mkdir -p /var/lib/cluebringer
sudo chown cbpolicyd:cbpolicyd /var/lib/cluebringer

# Initialize the SQLite database
cd /usr/share/doc/cluebringer/database
for i in core.tsql access_control.tsql quotas.tsql amavis.tsql checkhelo.tsql checkspf.tsql greylisting.tsql accounting.tsql; do
    sudo /usr/local/sbin/cbpadmin --config=/etc/cluebringer/cluebringer.conf --convert="$i" --output-driver=SQLite > /tmp/"$i".sql 2>/dev/null
done

# Import schemas into SQLite
for i in /tmp/*.tsql.sql; do
    sudo sqlite3 /var/lib/cluebringer/cbpolicyd.db < "$i"
done

sudo chown cbpolicyd:cbpolicyd /var/lib/cluebringer/cbpolicyd.db
sudo chmod 660 /var/lib/cluebringer/cbpolicyd.db

Verify the database was created:

sudo sqlite3 /var/lib/cluebringer/cbpolicyd.db ".tables"

You should see tables like policies, policy_members, quotas, greylisting, and others.

Option B - MariaDB/MySQL (Production Setup)

For busy mail servers or multi-server environments, use MariaDB.

Start and enable MariaDB:

sudo systemctl enable --now mariadb
sudo systemctl status mariadb

Secure the installation:

sudo mysql_secure_installation

Create the cbpolicyd database and user:

sudo mysql -u root -p <<EOF
CREATE DATABASE cluebringer;
CREATE USER 'cluebringer'@'localhost' IDENTIFIED BY 'YourStrongPasswordHere';
GRANT ALL PRIVILEGES ON cluebringer.* TO 'cluebringer'@'localhost';
FLUSH PRIVILEGES;
EOF

Import the database schema:

cd /usr/share/doc/cluebringer/database
for i in core.tsql access_control.tsql quotas.tsql amavis.tsql checkhelo.tsql checkspf.tsql greylisting.tsql accounting.tsql; do
    sudo /usr/local/sbin/cbpadmin --config=/etc/cluebringer/cluebringer.conf --convert="$i" --output-driver=mysql > /tmp/"$i".sql 2>/dev/null
done

for i in /tmp/*.tsql.sql; do
    sudo mysql -u root -p cluebringer < "$i"
done

Verify the tables were created:

sudo mysql -u root -p -e "USE cluebringer; SHOW TABLES;"

Step 5 - Configure cbpolicyd

Edit the main configuration file:

sudo vi /etc/cluebringer/cluebringer.conf

Here are the key sections you need to set. Adjust based on your chosen database backend.

Server settings:

[server]
# Listen on localhost port 10031 (Postfix will connect here)
host=127.0.0.1
port=10031

# Run as cbpolicyd user
user=cbpolicyd
group=cbpolicyd

# PID file location
pid_file=/run/cluebringer/cbpolicyd.pid

For SQLite backend:

[database]
DSN=DBI:SQLite:dbname=/var/lib/cluebringer/cbpolicyd.db

For MariaDB/MySQL backend:

[database]
DSN=DBI:mysql:database=cluebringer;host=localhost
Username=cluebringer
Password=YourStrongPasswordHere

Enable the modules you need:

[policies]
enable=1

[quotas]
enable=1

[greylisting]
enable=1

[checkhelo]
enable=1

[checkspf]
enable=1

[accounting]
enable=1

[accesscontrol]
enable=1

Create the PID directory:

sudo mkdir -p /run/cluebringer
sudo chown cbpolicyd:cbpolicyd /run/cluebringer

To make the PID directory persist across reboots, create a tmpfiles.d entry:

echo 'd /run/cluebringer 0755 cbpolicyd cbpolicyd -' | sudo tee /etc/tmpfiles.d/cluebringer.conf

Step 6 - Create a systemd Service Unit

Since we installed from source, we need to create a systemd unit file:

sudo tee /etc/systemd/system/cbpolicyd.service <<'EOF'
[Unit]
Description=cbpolicyd - Cluebringer Policy Daemon for Postfix
After=network.target mariadb.service

[Service]
Type=forking
User=cbpolicyd
Group=cbpolicyd
PIDFile=/run/cluebringer/cbpolicyd.pid
ExecStart=/usr/local/sbin/cbpolicyd --config /etc/cluebringer/cluebringer.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RuntimeDirectory=cluebringer

[Install]
WantedBy=multi-user.target
EOF

Reload systemd, then enable and start cbpolicyd:

sudo systemctl daemon-reload
sudo systemctl enable --now cbpolicyd

Verify the service is running:

sudo systemctl status cbpolicyd

Confirm it is listening on port 10031:

sudo ss -tlnp | grep 10031

Expected output:

LISTEN  0  128  127.0.0.1:10031  0.0.0.0:*  users:(("cbpolicyd",pid=XXXX,fd=4))

Step 7 - Integrate cbpolicyd with Postfix

Now tell Postfix to consult cbpolicyd for policy decisions. Edit the Postfix main configuration:

sudo vi /etc/postfix/main.cf

Add the policy service to smtpd_recipient_restrictions. Place it after your basic permit/reject rules:

smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    check_policy_service inet:127.0.0.1:10031

If you also want cbpolicyd to check at the end-of-data stage (needed for quotas that count message size), add this as well:

smtpd_end_of_data_restrictions =
    check_policy_service inet:127.0.0.1:10031

You can also set a timeout for the policy service so Postfix does not hang if cbpolicyd is slow:

smtpd_policy_service_max_idle = 300
smtpd_policy_service_max_ttl = 600

For Zimbra Mail Servers

If you are running Zimbra, cbpolicyd is typically bundled with the Zimbra installation. To enable it in Zimbra, run these as the zimbra user:

su - zimbra
zmprov ms $(zmhostname) +zimbraServiceEnabled cbpolicyd
zmprov mcf zimbraCBPolicydQuotasEnabled TRUE
zmcbpolicydctl start

For standalone Postfix installations, reload Postfix to apply changes:

sudo postfix check
sudo systemctl reload postfix

Verify Postfix reloaded cleanly:

sudo systemctl status postfix

Step 8 - Test Policy Enforcement

Test that cbpolicyd is responding to policy queries by connecting directly:

echo -e "request=smtpd_access_policy\nprotocol_state=RCPT\nprotocol_name=SMTP\nclient_address=192.168.1.100\nclient_name=test.example.com\nhelo_name=test.example.com\[email protected]\[email protected]\n\n" | nc 127.0.0.1 10031

You should get a response like:

action=dunno

The dunno response means cbpolicyd has no objection - the message is allowed through. If greylisting is active for new senders, you may see action=defer_if_permit instead, which is correct behavior.

You can also test by sending a real email through your server and checking the mail log:

sudo journalctl -u postfix -f

Look for lines mentioning policyd or check_policy_service.

Step 9 - Configure Rate Limiting Policies

Rate limiting is one of the most valuable features of cbpolicyd. It stops compromised accounts from sending thousands of spam messages before you notice.

Insert rate limiting policies directly into the database. The following example limits each authenticated sender to 100 messages per hour.

For SQLite

sudo sqlite3 /var/lib/cluebringer/cbpolicyd.db <<'EOF'
-- Create a policy for rate limiting
INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Rate Limit Outbound', 10, 'Limit outbound messages per sender', 0);

-- Add policy member - applies to all authenticated senders
INSERT INTO policy_members (PolicyID, Source, Destination, Comment)
VALUES (last_insert_rowid(), '%internal_ips', '%all', 'All internal senders');

-- Create quota for the policy
INSERT INTO quotas (PolicyID, Name, Track, Period, Verdict, Data)
VALUES (
    (SELECT ID FROM policies WHERE Name='Rate Limit Outbound'),
    'Sender Hourly Limit',
    'SenderAddress',
    3600,
    'REJECT',
    'Rate limit exceeded - max 100 messages per hour'
);

-- Set the actual limit
INSERT INTO quotas_limits (QuotasID, Type, CounterLimit)
VALUES (
    (SELECT ID FROM quotas WHERE Name='Sender Hourly Limit'),
    'MessageCount',
    100
);
EOF

For MariaDB/MySQL

sudo mysql -u root -p cluebringer <<'EOF'
INSERT INTO policies (Name, Priority, Description, Disabled)
VALUES ('Rate Limit Outbound', 10, 'Limit outbound messages per sender', 0);

SET @policy_id = LAST_INSERT_ID();

INSERT INTO policy_members (PolicyID, Source, Destination, Comment)
VALUES (@policy_id, '%internal_ips', '%all', 'All internal senders');

INSERT INTO quotas (PolicyID, Name, Track, Period, Verdict, Data)
VALUES (@policy_id, 'Sender Hourly Limit', 'SenderAddress', 3600, 'REJECT', 'Rate limit exceeded - max 100 messages per hour');

SET @quota_id = LAST_INSERT_ID();

INSERT INTO quotas_limits (QuotasID, Type, CounterLimit)
VALUES (@quota_id, 'MessageCount', 100);
EOF

You can add additional quotas for daily limits, message size limits, or per-domain restrictions. Some common examples:

# Daily limit of 500 messages per sender
INSERT INTO quotas (PolicyID, Name, Track, Period, Verdict, Data)
VALUES (@policy_id, 'Sender Daily Limit', 'SenderAddress', 86400, 'REJECT', 'Daily send limit exceeded');

INSERT INTO quotas_limits (QuotasID, Type, CounterLimit)
VALUES (LAST_INSERT_ID(), 'MessageCount', 500);

# Limit cumulative message size to 500MB per day per sender
INSERT INTO quotas (PolicyID, Name, Track, Period, Verdict, Data)
VALUES (@policy_id, 'Sender Size Limit', 'SenderAddress', 86400, 'REJECT', 'Daily size quota exceeded');

INSERT INTO quotas_limits (QuotasID, Type, CounterLimit)
VALUES (LAST_INSERT_ID(), 'MessageCumulativeSize', 524288000);

After adding policies, restart cbpolicyd to pick up changes:

sudo systemctl restart cbpolicyd
sudo systemctl status cbpolicyd

Step 10 - Set Up the Web Management Interface (Optional)

cbpolicyd includes a PHP-based web UI called the Cluebringer webui. It makes managing policies, quotas, and greylisting much easier than running SQL by hand.

Install Apache and PHP:

sudo dnf install -y httpd php php-mysqlnd php-pdo php-gd

Copy the web UI files from the source directory:

sudo cp -r /usr/local/src/cluebringer-v2.1.x-201505/webui /var/www/html/cluebringer

Configure the web UI database connection:

sudo vi /var/www/html/cluebringer/includes/config.php

Set the database connection parameters to match your backend:

$DB_DSN = "mysql:host=localhost;dbname=cluebringer";
$DB_USER = "cluebringer";
$DB_PASS = "YourStrongPasswordHere";

For SQLite, use:

$DB_DSN = "sqlite:/var/lib/cluebringer/cbpolicyd.db";

Set proper permissions and configure SELinux:

sudo chown -R apache:apache /var/www/html/cluebringer
sudo setsebool -P httpd_can_network_connect_db 1

Start Apache:

sudo systemctl enable --now httpd
sudo systemctl status httpd

If the firewall is active, allow HTTP access:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload

Access the web UI at http://your-server-ip/cluebringer. I strongly recommend restricting access to this interface using Apache authentication or IP-based access controls. Do not expose it to the public internet.

Step 11 - Configure SELinux for cbpolicyd

RHEL 10 runs SELinux in enforcing mode by default. cbpolicyd may need a custom policy to work properly.

Check for SELinux denials:

sudo ausearch -m avc -ts recent | grep cbpolicyd

If you see denials, generate and install a custom policy module:

sudo ausearch -m avc -ts recent | audit2allow -M cbpolicyd_custom
sudo semodule -i cbpolicyd_custom.pp

Restart cbpolicyd and verify no more denials appear:

sudo systemctl restart cbpolicyd
sudo ausearch -m avc -ts recent | grep cbpolicyd

Troubleshooting

Here are common issues you may run into and how to fix them.

cbpolicyd fails to start

Check the logs for specific errors:

sudo journalctl -u cbpolicyd -e --no-pager
sudo tail -50 /var/log/maillog

Common causes:

  • Missing Perl modules - Install whichever module the error message names: sudo dnf install perl-Module-Name or sudo cpanm Module::Name
  • Database connection failed - Verify your DSN, username, and password in /etc/cluebringer/cluebringer.conf
  • PID directory missing - Run sudo mkdir -p /run/cluebringer && sudo chown cbpolicyd:cbpolicyd /run/cluebringer
  • Permission denied - Check file ownership: sudo chown -R cbpolicyd:cbpolicyd /etc/cluebringer /var/lib/cluebringer

Postfix cannot connect to cbpolicyd

# Verify cbpolicyd is listening
sudo ss -tlnp | grep 10031

# Test the connection manually
nc -zv 127.0.0.1 10031

# Check that Postfix main.cf has the correct policy service line
sudo postconf smtpd_recipient_restrictions

Greylisting causing excessive mail delays

If legitimate mail is delayed too long by greylisting, you can adjust the greylisting period or whitelist known senders:

# Reduce greylisting delay from default (5 minutes example)
sudo sqlite3 /var/lib/cluebringer/cbpolicyd.db \
  "UPDATE greylisting SET UseGreylisting=1, GreylistPeriod=120 WHERE ID=1;"

# Or for MariaDB
sudo mysql -u root -p -e "USE cluebringer; UPDATE greylisting SET UseGreylisting=1, GreylistPeriod=120 WHERE ID=1;"

Rate limits not being enforced

Verify quotas are enabled in the configuration file and the module is loaded:

grep -i "quotas" /etc/cluebringer/cluebringer.conf

Make sure smtpd_end_of_data_restrictions is configured in Postfix if you are using size-based quotas. Also verify your policies are not marked as Disabled in the database:

# SQLite
sudo sqlite3 /var/lib/cluebringer/cbpolicyd.db "SELECT * FROM policies;"

# MariaDB
sudo mysql -u root -p -e "USE cluebringer; SELECT * FROM policies;"

Checking cbpolicyd version and module status

/usr/local/sbin/cbpolicyd --version 2>&1 || echo "Check logs for version info"

Conclusion

You now have cbpolicyd running on RHEL 10, Rocky Linux 10, or AlmaLinux 10, integrated with Postfix for policy-based mail control. The combination of rate limiting, greylisting, and SPF checks gives you solid protection against spam and abuse without adding heavy overhead to your mail server.

For production environments, I recommend starting with conservative rate limits and monitoring your mail logs closely for a week before tightening the policies. Keep an eye on /var/log/maillog and journalctl -u cbpolicyd to see how policies affect your mail flow. Adjust your greylisting and quota settings based on your actual traffic patterns rather than guessing.

If you are running Zimbra, remember that Zimbra ships its own cbpolicyd build - use the Zimbra admin console or the zmprov commands shown above instead of managing the standalone installation alongside it.

LEAVE A REPLY

Please enter your comment!
Please enter your name here