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-Nameorsudo 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.





























































