Back to Cheatsheets
Cheatsheet

Proxmox Mail Gateway (PMG) Cheatsheet

Proxmox Mail Gateway (PMG) is a mail gateway and spam filter. This cheatsheet covers daily operations and advanced configuration.

Proxmox Mail Gateway (PMG) Architecture

PMG Overview

# PMG sits between the internet and your mail server
# Internet -> PMG (port 25) -> Internal Mail Server (port 2525 or custom)
 
# Default PMG ports
# 25 (SMTP inbound), 8006 (Web UI), 40001 (Cluster sync), 22 (SSH)
 
# Access web UI
# https://pmg.example.com:8006
 
# Default credentials: admin@pam (system root password)
 
# Check PMG version
pmgversion

Transport Rules

# Transport rules route mail to internal mail servers
# /etc/pmg/transport
 
# Format: domain target
example.com           smtp:192.168.1.10:25
example.org           smtp:192.168.1.11:25
sub.example.com       smtp:192.168.1.12:2525
 
# Wildcard domain
.example.com          smtp:192.168.1.10:25
 
# Catch-all (any domain not matched above)
*                     smtp:192.168.1.10:25
 
# Apply transport rules
pmg transport reload

Mail Flow

# Standard flow:
# 1. Internet sends mail to PMG port 25
# 2. PMG runs SpamAssassin, ClamAV, rule system
# 3. PMG forwards clean mail to internal mail server
# 4. Quarantined mail is stored for review
 
# Verify mail flow in tracking center
# Administration > Tracking Center
 
# Or via CLI
pmg log view

PMG Queue Management

pmgqm (Queue Manager)

# Show queue statistics
pmgqm stats
 
# Show detailed queue state
pmgqm status
 
# List queued messages
pmgqm list
 
# List with filters
pmgqm list --sender [email protected]
pmgqm list --receiver [email protected]
 
# Show a specific message
pmgqm show <queue-id>
 
# Resend a message (attempt delivery again)
pmgqm resend <queue-id>
 
# Delete a message
pmgqm delete <queue-id>
 
# Delete all messages from a sender
pmgqm delete --sender [email protected]
 
# Flush the queue (attempt all deliveries)
pmgqm flush
 
# Move message to quarantine
pmgqm quarantine <queue-id>

Queue Statistics

# Active queue count
pmgqm stats | grep active
 
# Deferred count
pmgqm stats | grep deferred
 
# View deferred queue details
pmgqm list --status deferred
 
# Show queue history
pmgqm history
 
# Show per-domain queue stats
pmgqm list | awk '{print $3}' | sort | uniq -c | sort -rn
 
# Remove old messages from queue
pmgqm delete --received-before "-7 days"

Quarantine

Managing Quarantine

# Access quarantine web UI
# https://pmg.example.com:8006/#/quarantine
 
# List quarantined messages (CLI)
pmgqm list --status quarantine
 
# Release a quarantined message to recipient
pmgqm release <queue-id>
 
# Release and whitelist sender
pmgqm release --whitelist <queue-id>
 
# Whitelist a sender (bypass quarantine)
# Administration > Quarantine > Whitelist
# Or via CLI
pmg whitelist add [email protected]
 
# Blacklist a sender
pmg blacklist add [email protected]
 
# View whitelist
pmg whitelist list
 
# View blacklist
pmg blacklist list
 
# Remove from whitelist
pmg whitelist remove [email protected]
 
# Configure quarantine retention
# pmg config set --quarantine-retention 30

Quarantine Reports & Notifications

# Send quarantine digest to users
pmgqm send-digest
 
# Configure digest schedule
# pmg config set --digest-daily 1
# pmg config set --digest-weekly 0
 
# Generate quarantine statistics
pmgqm stats --quarantine
 
# Query quarantine by time range
pmgqm list --status quarantine --received-after "-24 hours"

Rule System

Understanding Objects

# Rules consist of: What + Who + When + Action
 
# What: Spam score threshold, virus, specific headers
# Who: Sender, recipient, domain
# When: Time of day, day of week
# Action: Block, quarantine, pass, redirect
 
# List rules
pmg rule list
 
# Show a specific rule's details
pmg rule show <rule-id>
 
# Enable/disable a rule
pmg rule enable <rule-id>
pmg rule disable <rule-id>
 
# Reorder rules (lower priority number runs first)
pmg rule move <rule-id> --position 1

Creating Rules (CLI)

# Block mail with spam score >= 5 from external senders
pmg rule create --name "Block High Spam" \
  --what "spamscore >= 5" \
  --who "!from_local" \
  --action "Block"
 
# Quarantine mail with spam score 3-5
pmg rule create --name "Quarantine Medium Spam" \
  --what "spamscore >= 3 and spamscore < 5" \
  --who "!from_local" \
  --action "Quarantine"
 
# Block all mail from specific country
pmg rule create --name "Block Country" \
  --what "country == CN or country == RU" \
  --action "Block"
 
# Whitelist specific sender (always deliver)
pmg rule create --name "Whitelist Partner" \
  --what "sender == '@partner.com'" \
  --action "Accept"
 
# Redirect mail from specific sender
pmg rule create --name "Redirect Executive Mail" \
  --what "sender == '[email protected]'" \
  --action "Redirect to [email protected]"
 
# Block attachments by extension
pmg rule create --name "Block Executables" \
  --what "filename matches '.exe$' or filename matches '.scr$'" \
  --action "Block"

Custom Rules (Advanced)

# Combine conditions
# Block if spam score high AND from unknown sender AND contains specific word
--what "spamscore >= 5 AND !from_local AND body contains 'viagra'"
 
# Time-based rules
--when "time 08:00-18:00"     # Only apply during work hours
--when "day Mon-Fri"          # Only apply weekdays
 
# Multiple actions per rule
# Use action sets: Action: Quarantine + Notify admin
 
# Log-only rule (for testing before enforcing)
pmg rule create --name "Test Rule" \
  --what "spamscore >= 2" \
  --action "Log"

SpamAssassin Tuning

Bayes Learning

# Enable Bayesian filtering
# Navigate to Mail Filter > SpamDetector > Bayes in web UI
# Or configure in /etc/mail/spamassassin/local.cf
 
# Train Bayes with known spam
sa-learn --spam /var/spam/*.emails
 
# Train Bayes with known ham (legitimate mail)
sa-learn --ham /var/ham/*.emails
 
# Auto-learn (PMG automatically learns from quarantined/released mail)
# Settings: Mail Filter > SpamDetector > Bayes > Auto-learn
 
# Show Bayes statistics
sa-learn --dump magic
 
# Reset Bayes database
sa-learn --clear
sa-learn --rebuild
 
# Bayes database location
ls -la /var/lib/amavis/.spamassassin/bayes_*

Custom Scores

# Edit SpamAssassin local configuration
# /etc/mail/spamassassin/local.cf
 
# Adjust base score thresholds
required_score 5.0          # Default spam threshold (lower = more aggressive)
tag_level 2.0               # Add spam headers above this score
tag2_level 6.0              # Mark as high spam above this score
 
# Rewrite specific rules scores
score RCVD_IN_SBL_CSS       3.0 1.5 3.0 1.5   # Raise SBL blacklist score
score RCVD_IN_PBL           1.5 0.0 1.5 0.0   # Lower PBL score
 
# Disable a rule
score RCVD_IN_PBL           0 0 0 0
 
# Enable short-circuiting (skip rules if threshold already hit)
use_bayes_rules 1
shortcircuit USER_IN_WHITELIST on
shortcircuit USER_IN_DEF_WHITELIST on
shortcircuit ALL_TRUSTED on
 
# Add custom rules
# /etc/mail/spamassassin/custom.cf
header LOCAL_HAS_X_PRIORITY    X-Priority =~ /^1/
describe LOCAL_HAS_X_PRIORITY  High priority header
score    LOCAL_HAS_X_PRIORITY  0.5
 
# After editing, restart services
systemctl restart pmg-smtpd-filter
systemctl restart pmg-smtp-filter

Razor / DCC / Pyzor

# Install collaborative filtering components
apt install -y razor pyzor dcc-client
 
# Configure Razor
razor-admin -create
razor-admin -register -user [email protected] -pass $(openssl rand -base64 12)
 
# Configure Pyzor
pyzor discover
 
# Configure DCC (ifdcc)
# /etc/dcc/dcc_conf
DCCM_LOG_AT=never
DCCM_REJECT_AT=many
 
# Enable in SpamAssassin
# /etc/mail/spamassassin/local.cf
use_razor 1
use_pyzor 1
use_dcc 1
dcc_dccifd_path /var/run/dcc/dccifd
 
# Test DCC connectivity
cdcc "info"
 
# Restart after enabling
systemctl restart pmg-smtpd-filter

Cluster Management

pmgcm Commands

# Create a PMG cluster
pmgcm create my-cluster
 
# Join a node to the cluster
pmgcm join master.example.com
 
# Show cluster status
pmgcm status
 
# Show cluster nodes
pmgcm nodes
 
# Remove a node
pmgcm delete <node-name>
 
# Sync cluster configuration
pmgcm sync
 
# Force cluster sync
pmgcm sync --force
 
# Show cluster config
pmgcm config

Cluster Synchronization

# Cluster sync ports: 40001 (TCP, pmgcm)
# Ensure firewall allows this between nodes
 
# Manual sync of config
pmgcm sync --from master.example.com
 
# Check last sync time
journalctl -u pmgcm -n 10 | grep "sync"
 
# Resolve sync conflicts
# Configuration is stored in /etc/pmg/
# Master configuration takes priority
 
# Transfer certificates between nodes
# Administration > Certificates > Cluster Sync

TLS Certificates

# Generate Let's Encrypt certificate
# Administration > Certificates > ACME
 
# Or manually upload certificate
# /etc/pmg/pmg-tls.pem (certificate)
# /etc/pmg/pmg-tls.key (private key)
 
# After changing certificates, restart
systemctl restart pmgproxy
 
# Verify certificate
openssl s_client -connect pmg.example.com:8006 -showcerts
 
# Generate self-signed certificate
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout /etc/pmg/pmg-tls.key \
  -out /etc/pmg/pmg-tls.pem \
  -days 3650 -subj "/CN=pmg.example.com"
 
# Set permissions
chmod 640 /etc/pmg/pmg-tls.key

Logs & Debugging

PMG Log Commands

# View PMG logs (web UI equivalent)
pmg log view
 
# View real-time log
pmg log watch
 
# View logs with filters
pmg log view --since "-1 hour"
pmg log view --since "2024-01-01" --until "2024-01-02"
 
# View logs for specific sender
pmg log view --sender [email protected]
 
# View logs for specific recipient
pmg log view --receiver [email protected]
 
# Save logs to file
pmg log view --output /tmp/pmg-log.txt

Tracking Center

# Web UI: Administration > Tracking Center
 
# Search by message ID (from email headers)
# Tracking Center > Search > Message-ID: <...>
 
# Search by sender/receiver
# Tracking Center > Search > Sender or Receiver
 
# Search by date range
# Tracking Center > Search > Date Range
 
# View message processing details
# Click on a message to see:
#   - SpamAssassin score breakdown
#   - Rule matches
#   - Virus scan results
#   - Delivery status

Grep Patterns

# PMG filter logs
grep -i "spam" /var/log/pmg-smtpd-filter.log
grep -i "virus" /var/log/pmg-smtpd-filter.log
 
# Connection logs
grep "connect from" /var/log/mail.log
grep "disconnect" /var/log/mail.log
 
# Rule matches
grep "RULE:" /var/log/pmg.log
 
# Quarantine actions
grep "quarantine" /var/log/pmg.log
 
# Delivery failures
grep "status=deferred\|status=bounced" /var/log/mail.log
 
# TLS connection failures
grep "TLS\|tls" /var/log/mail.log | grep -i "error\|fail"
 
# All errors
grep -i "error\|warning\|fatal" /var/log/pmg.log
 
# SpamAssassin score details
grep "spam," /var/log/mail.log | head -5

DKIM Signing in PMG

Configuring DKIM

# PMG can sign outgoing mail with DKIM
# Administration > Mail Proxy > DKIM
 
# Add DKIM key pair
# Administration > Mail Proxy > DKIM > Add
# Select domain, upload private key, set selector
 
# Or via CLI
pmg config dkim add --domain example.com \
  --selector 202401 \
  --keyfile /etc/pmg/dkim/example.com.key
 
# List configured DKIM keys
pmg config dkim list
 
# Remove DKIM key
pmg config dkim remove --domain example.com
 
# Test DKIM signing
# Send test email: pmg will sign outgoing mail automatically
# Check headers for "DKIM-Signature:"

Backup & Restore PMG Config

Backup PMG

# Backup entire PMG configuration
tar czf /root/pmg-backup-$(date +%Y%m%d).tar.gz \
  /etc/pmg/ \
  /var/lib/pmg/ \
  /var/spool/pmg/ \
  /etc/network/ \
  /etc/hosts \
  /etc/apt/sources.list \
  /etc/apt/sources.list.d/
 
# Backup specific components
# Config only
tar czf pmg-config-$(date +%Y%m%d).tar.gz /etc/pmg/
 
# Quarantine data
tar czf pmg-quarantine-$(date +%Y%m%d).tar.gz /var/spool/pmg/quarantine/
 
# SpamAssassin bayes database
tar czf pmg-bayes-$(date +%Y%m%d).tar.gz /var/lib/amavis/.spamassassin/
 
# PMG built-in backup tool
# Administration > Backup

Restore PMG

# Restore from config backup
systemctl stop pmg-smtpd-filter pmg-smtp-filter pmgproxy pmgdaemon
tar xzf pmg-backup-20240101.tar.gz -C /
systemctl start pmgdaemon pmgproxy pmg-smtpd-filter pmg-smtp-filter
 
# Restore specific config files
tar xzf pmg-config-20240101.tar.gz -C /etc/pmg/
 
# After restore, verify everything
pmgversion
pmgcm status
systemctl status pmg-smtpd-filter
 
# Re-import database (if backed up separately)
pmgdb restore /path/to/pmg-database.sql