Created page with "KitsNet Migration from eFa Project v5 on cinzano to Proxmox Mail Gateway on dusse {{DISPLAYTITLE:KitsNet Migration from eFa Project v5 on cinzano to Proxmox Mail Gateway on dusse}} __TOC__ = Document status and purpose = This document is the detailed build, configuration, validation, migration, rollback, and operating procedure for replacing the existing KitsNet eFa Project v5 perimeter mail gateway on '''cinzano''' with '''Proxmox Mail Gateway (PMG) installed direct..." |
|||
(No difference)
| |||
Revision as of 09:40, 9 September 2026
KitsNet Migration from eFa Project v5 on cinzano to Proxmox Mail Gateway on dusse
1 Document status and purpose[edit | edit source]
This document is the detailed build, configuration, validation, migration, rollback, and operating procedure for replacing the existing KitsNet eFa Project v5 perimeter mail gateway on cinzano with Proxmox Mail Gateway (PMG) installed directly in the virtual machine dusse.
It is written for a Linux administrator who knows the KitsNet environment but is not yet familiar with Proxmox Mail Gateway.
The intended result is functional equivalence with the important behavior of cinzano:
- Inspect all inbound mail for spam, malware, phishing, dangerous attachments, malformed content, and protocol abuse.
- Verify recipients before accepting inbound mail.
- Deliver approved inbound mail to mailroom.lan.kitsnet.us.
- Inspect all outbound mail.
- Send all approved outbound mail through outbound.mailhop.org:2525 using a username and password.
- Leave final DKIM signing to DuoCircle MailHop.
- Prevent external recipients from seeing the internal client, internal workstation address, mailroom host, or other internal routing history.
- Provide message tracking, quarantines, statistics, graphs, daily reports, and operational monitoring analogous to the current MailScanner/MailWatch system.
- Permit controlled testing before either inbound or outbound production mail is moved.
- Preserve a simple and rapid rollback path to cinzano.
2 Correct system topology[edit | edit source]
There is no Proxmox VE host named dusse and there is no nested PMG virtual machine inside dusse.
The actual topology is:
wort.lan.kitsnet.us
Physical Rocky Linux/KVM host
|
+-- cinzano VM
| Current eFa Project v5 gateway
| Current address: 192.168.15.83
|
+-- ciroc VM
| Current internal mail server
| Service name: mailroom.lan.kitsnet.us
| Current address: 192.168.15.58
|
+-- dusse VM
Proxmox Mail Gateway is installed directly in this VM
Existing administrative name: dusse.lan.kitsnet.us
Address: 192.168.15.92
The libvirt/KVM guest name remains dusse on wort. The PMG operating-system hostname and SMTP identity are separate from the KVM guest name and are addressed later in this document.
The current public address observed for KitsNet mail is:
72.225.204.20
The public DNS currently has the following relevant structure:
kitsnet.us. MX 10 kitsnet.us. kitsnet.us. A 72.225.204.20 mailgw.kitsnet.us. CNAME kitsnet.us.
This means inbound production cutover normally requires changing the perimeter NAT destination for TCP port 25. It does not require changing the public MX record, public address, SPF record, or public PTR record, provided the same public address remains in use.
3 Product identity check: perform this first[edit | edit source]
The word “Proxmox” can refer to either Proxmox Virtual Environment or Proxmox Mail Gateway. This procedure assumes that Proxmox Mail Gateway is installed directly on dusse.
Log into the dusse console through wort, or SSH to 192.168.15.92, and run:
hostnamectl
pmgversion -v
command -v pmgconfig
command -v pmgsh
command -v pveversion || true
The expected result is:
pmgversion -vworks.pmgconfigandpmgshexist.- The installed packages identify the system as Proxmox Mail Gateway.
- It is normal for
pveversionto be absent.
STOP CONDITION: If pmgversion does not exist and the system instead identifies itself as Proxmox VE, the wrong product is installed for this design. Do not continue until PMG has been installed.
At the time this document was prepared, the current documented release was PMG 9.1.1. The production system should be fully updated within the supported PMG 9.1 release family before cutover.
4 Current cinzano behavior established from the configuration and message captures[edit | edit source]
4.1 Inbound flow[edit | edit source]
Internet sender
|
| TCP 25
v
cinzano / eFa / MailScanner
|
| SMTP with TLS
v
mailroom.lan.kitsnet.us on ciroc
|
| LMTP/local delivery
v
User mailbox
Current inbound protections include:
- Greylisting through SQLGrey.
- Spamhaus ZEN RBL checking.
- SPF checking.
- OpenDKIM verification.
- OpenDMARC verification and policy processing.
- MailScanner processing.
- SpamAssassin.
- ClamAV.
- Third-party ClamAV signature feeds through
clamav-unofficial-sigs. - Recipient verification against mailroom.
- Sender-domain and HELO checks.
- Attachment filename, file-content, and archive rules.
- Quarantine.
- Tracking and reporting through MailWatch.
4.2 Outbound flow[edit | edit source]
Outlook or another internal sender
|
v
mailroom.lan.kitsnet.us on ciroc
|
v
cinzano / eFa / MailScanner
|
| TLS and SMTP AUTH
v
outbound.mailhop.org:2525
|
| DKIM signing
v
Internet recipient
The current Postfix configuration uses:
smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_sasl_security_options = noanonymous
The current transport table sends non-KitsNet destinations through:
relay:[outbound.mailhop.org]:2525
DuoCircle MailHop performs final DKIM signing for at least:
kitsnet.uskitsnet.vancouver.bc.ca
PMG DKIM signing must therefore remain disabled.
4.3 Current internal-header concealment[edit | edit source]
Cinzano strips Received: headers referring to localhost and systems under lan.kitsnet.us. The captured outbound message proves that the final external recipient sees:
- The visible gateway hop.
- MailHop.
- The destination provider.
The external recipient does not see:
mailroom.lan.kitsnet.usciroc.lan.kitsnet.us- The Outlook workstation
lafite.lan.kitsnet.us - The workstation private address.
- Other internal routing history.
This is an explicit migration acceptance requirement.
4.4 Current spam and quarantine thresholds[edit | edit source]
Cinzano currently uses:
Required SpamAssassin Score = 4 High SpamAssassin Score = 7 Spam Actions = store High Scoring Spam Actions = store
Both ordinary spam and high-scoring spam are stored rather than delivered.
4.5 Current antivirus and dangerous-content behavior[edit | edit source]
Important current MailScanner settings include:
Virus Scanning = yes Quarantine Infections = yes Quarantine Whole Message = yes Deliver Cleaned Messages = No Sign Clean Messages = No Detailed Spam Report = yes Block Encrypted Messages = no
Cinzano also denies many dangerous filename and content types, including executables, scripts, Windows shortcuts, registry data, self-extracting archives, and executable content hidden inside archives.
4.6 Current maximum message size[edit | edit source]
Cinzano is configured for:
message_size_limit = 133169152
PMG defaults to a much smaller maximum. The PMG limit must be raised or users will experience a major behavior change.
4.7 Current web and monitoring functions[edit | edit source]
Cinzano currently provides:
- MailWatch message search and detailed message records.
- Spam and virus reports.
- Quarantine access.
- Mail volume and classification graphs.
- NGINX Proxy Manager publication of
mailgw.kitsnet.us. - Zabbix monitoring.
- Syslog/ConsoleWorks integration.
- Special suppression of outgoing OpenDMARC aggregate reports.
- Real source-IP logging through the reverse proxy.
PMG does not generate standalone OpenDMARC aggregate reports in the same way as cinzano, so the existing daily-DMARC suppression customization is not required on PMG.
5 Important design decisions[edit | edit source]
5.1 PMG runs directly on dusse[edit | edit source]
There is no additional virtual-machine creation step. All PMG configuration in this document is performed directly on:
dusse VM 192.168.15.92
5.2 Recommended SMTP identity[edit | edit source]
PMG uses the system hostname to construct the FQDN placed in Postfix. Leaving the operating-system FQDN as dusse.lan.kitsnet.us risks exposing the internal lan.kitsnet.us namespace in SMTP banners and gateway-generated headers.
The recommended production mail identity is:
mailgw.kitsnet.us
The KVM guest remains named dusse on wort, and the administrative alias can continue to resolve internally:
dusse.lan.kitsnet.us -> 192.168.15.92
The resulting name plan is:
| Function | Name |
|---|---|
| KVM/libvirt guest name | dusse
|
| Internal administrative alias | dusse.lan.kitsnet.us
|
| PMG operating-system and SMTP FQDN | mailgw.kitsnet.us
|
| Public quarantine URL | https:=mailgw.kitsnet.us/
|
| Direct LAN administrative URL | https:=192.168.15.92:8006/
|
This works even though public DNS resolves mailgw.kitsnet.us to the public KitsNet address: the PMG system itself will have an /etc/hosts entry mapping its own FQDN to 192.168.15.92.
If retaining dusse.lan.kitsnet.us as the operating-system FQDN is mandatory, stop before changing the hostname and make a deliberate decision about a persistent Postfix myhostname template override. That would create another local customization requiring upgrade maintenance.
5.3 PMG port use[edit | edit source]
PMG distinguishes message direction by the listener port:
| Port | Purpose | Permitted source |
|---|---|---|
| TCP 25 | Untrusted inbound Internet mail | Internet through perimeter NAT; controlled external tests |
| TCP 26 | Trusted outbound mail entering PMG | Only mailroom/ciroc at 192.168.15.58 |
| TCP 8006 | PMG administration/API | LAN/VPN administrators only |
| TCP 443 | Quarantine-only reverse proxy installed on PMG | NGINX Proxy Manager only |
| TCP 22 | SSH administration | LAN/VPN administrators only |
| TCP 10050 | Zabbix agent | Zabbix server only |
5.4 Same-subnet relay risk must be addressed[edit | edit source]
PMG automatically treats hosts in its directly connected subnet as trusted relay clients. Dusse and most KitsNet systems share the 192.168.14.0/23 LAN. Therefore, relying only on PMG’s default network list would allow more internal hosts to relay than intended.
The host firewall in this guide deliberately:
- Rejects general LAN access to the untrusted TCP 25 listener.
- Allows TCP 25 from non-LAN sources arriving through the perimeter NAT.
- Allows TCP 26 only from mailroom at 192.168.15.58.
- Prevents any ordinary LAN host from using PMG as an outbound relay.
All internal applications should submit through mailroom, not directly through PMG.
5.5 Public administration is not exposed[edit | edit source]
The PMG administrative GUI remains directly accessible only on the LAN or VPN through port 8006.
A small NGINX service on PMG exposes only the quarantine-related paths on port 443. NGINX Proxy Manager forwards public mailgw.kitsnet.us traffic to that quarantine-only listener.
5.6 MailHop remains the DKIM signer[edit | edit source]
PMG outbound DKIM signing remains disabled. Internal-header hiding, antivirus scanning, spam scanning, and attachment processing all occur before MailHop signs the final message.
5.7 Initial migration uses after-queue filtering[edit | edit source]
Retain PMG’s default after-queue filtering model initially. It provides quarantine and deferred-delivery behavior closest to the existing eFa/MailScanner design.
Do not enable before-queue filtering until the production rules have been validated and the operational effect of SMTP-time rejection is understood.
6 Values used in this guide[edit | edit source]
Verify every value before applying configuration.
| Item | Value |
|---|---|
| Existing eFa gateway | cinzano
|
| Existing eFa address | 192.168.15.83
|
| New PMG VM/KVM name | dusse
|
| New PMG address | 192.168.15.92
|
| PMG production FQDN | mailgw.kitsnet.us
|
| Internal mail service | mailroom.lan.kitsnet.us
|
| Current mailroom host | ciroc.lan.kitsnet.us
|
| Current mailroom address | 192.168.15.58
|
| Public address | 72.225.204.20
|
| MailHop host | outbound.mailhop.org
|
| MailHop port | 2525
|
| PMG administrator mail | reroot@lan.kitsnet.us
|
| Zabbix server address observed in the old firewall | 192.168.15.54 — verify before use
|
| NPM source address | Determine before configuring the PMG firewall |
| Internal DNS server addresses | Determine before installing Unbound forwarding |
7 Preparation and rollback protection[edit | edit source]
7.1 Preserve cinzano[edit | edit source]
Do not modify or remove cinzano during PMG development.
Before starting:
- Confirm cinzano is processing mail normally.
- Confirm its Postfix queue is healthy.
- Preserve the existing cinzano configuration exports.
- Preserve the raw inbound and outbound test headers.
- Preserve the MailScanner/MailWatch database and quarantine until the migration has been accepted.
- Record the existing perimeter NAT rule for TCP 25.
- Record the existing mailroom outbound relay setting.
- Record the existing NPM
mailgw.kitsnet.usproxy configuration. - Record the existing Zabbix host configuration.
- Confirm that the MailHop username and password are available securely.
- Rotate any credentials exposed in screenshots or configuration archives before final production acceptance.
7.2 Back up dusse on wort[edit | edit source]
Use the existing KitsNet KVM/Veeam backup process to capture dusse before detailed PMG customization.
On wort, record at minimum:
virsh dominfo dusse
virsh dumpxml dusse > /root/dusse-before-pmg-config.xml
Store the XML somewhere protected. Complete the normal Veeam or VM-level backup before proceeding.
7.3 Capture a clean PMG baseline[edit | edit source]
On dusse:
mkdir -p /root/pmg-baseline
pmgversion -v > /root/pmg-baseline/pmgversion.txt
hostnamectl > /root/pmg-baseline/hostnamectl.txt
ip -brief address > /root/pmg-baseline/ip-address.txt
ip route > /root/pmg-baseline/ip-route.txt
cat /etc/hostname > /root/pmg-baseline/hostname.txt
cp -a /etc/hosts /root/pmg-baseline/hosts
cp -a /etc/resolv.conf /root/pmg-baseline/resolv.conf
cp -a /etc/network/interfaces /root/pmg-baseline/interfaces
pmgconfig dump > /root/pmg-baseline/pmgconfig-dump.txt
pmgsh get /config/mail > /root/pmg-baseline/mail-config.json
systemctl --failed > /root/pmg-baseline/failed-services.txt
8 Initial PMG access and update[edit | edit source]
8.1 Open the PMG web interface[edit | edit source]
From a LAN workstation, browse to:
https:=192.168.15.92:8006/
Accept the temporary self-signed certificate warning.
Log in with:
User: root Realm: Linux PAM Password: the root password chosen during installation
The PMG interface is not the Proxmox VE interface. Its navigation includes PMG functions such as:
- Dashboard
- Mail Filter
- Mail Proxy
- Spam Detector
- Virus Detector
- Quarantine
- Tracking Center
- Statistics
- Administration
8.2 Configure repositories and update[edit | edit source]
If there is no paid subscription:
- Open Administration.
- Open Repositories.
- Disable the enterprise repository if it is enabled without a subscription.
- Enable the PMG no-subscription repository offered by the interface.
- Open Updates.
- Click Refresh.
- Install all available updates.
- Reboot if a kernel or major system update was installed.
Also verify at the command line:
apt update
apt full-upgrade
pmgversion -v
systemctl --failed
Acceptance gate: Do not continue while any essential PMG service is failed.
9 Configure the PMG system hostname and mail identity[edit | edit source]
Perform this before configuring certificates, reverse proxies, or a PMG cluster.
9.1 Set the short hostname[edit | edit source]
On dusse:
printf '%s\n' 'mailgw' > /etc/hostname
hostnamectl set-hostname mailgw
9.2 Configure /etc/hosts[edit | edit source]
Preserve unrelated existing entries. Ensure the primary address has the FQDN first:
127.0.0.1 localhost.localdomain localhost
192.168.15.92 mailgw.kitsnet.us mailgw dusse.lan.kitsnet.us dusse
9.3 Configure the DNS search domain[edit | edit source]
Until Unbound is installed, configure the actual KitsNet internal DNS servers:
search kitsnet.us
nameserver INTERNAL_DNS_SERVER_1
nameserver INTERNAL_DNS_SERVER_2
This can also be configured through the PMG Network/DNS page.
9.4 Reboot and validate[edit | edit source]
reboot
After reboot:
hostname
hostname -f
getent hosts mailgw.kitsnet.us
getent hosts dusse.lan.kitsnet.us
pmgconfig dump | grep -E 'dns\.(hostname|domain)'
postconf myhostname
Expected:
hostname -> mailgw hostname -f -> mailgw.kitsnet.us postconf -> myhostname = mailgw.kitsnet.us
If hostname -f is wrong, stop and correct DNS, /etc/hosts, and the search domain before continuing.
10 Confirm and protect the network configuration[edit | edit source]
10.1 Verify the static address[edit | edit source]
In the PMG GUI, open the network configuration and confirm:
Address: 192.168.15.92 Gateway: the KitsNet LAN gateway Interface: the VirtIO interface presented by wort Prefix: verify the actual configured KitsNet prefix
At the shell:
ip -brief address
ip route
ping -c 3 192.168.15.58
Do not change the address unless a planned network redesign is being performed.
10.2 Determine the NPM source address[edit | edit source]
Before enabling the host firewall, determine the source address used by NGINX Proxy Manager when it connects to backend systems.
One method is to temporarily observe connections while opening an existing NPM-published service:
tcpdump -ni any tcp port 443
Record the address as NPM_SOURCE_IP.
10.3 Configure the host firewall[edit | edit source]
The same-subnet relay behavior makes this a security-critical step.
The safest procedure is:
- Work from the KVM console on wort, not only through SSH.
- Keep an existing root console open.
- Replace every placeholder before applying the rules.
- Syntax-check the rules.
- Apply them from the local console.
- Test administration, SMTP, NPM, and Zabbix immediately.
Install and enable nftables if needed:
apt install nftables
systemctl enable nftables
Create /root/nftables-pmg.conf using the actual administrator networks and NPM address:
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
iifname "lo" accept
ct state established,related accept
ct state invalid drop
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
# Prevent same-subnet clients from using the untrusted listener as a relay.
# All internal applications must send through mailroom instead.
ip saddr 192.168.14.0/23 tcp dport 25 drop
# Public inbound SMTP. The perimeter DNAT preserves the external source IP.
tcp dport 25 accept
# Trusted outbound entry: mailroom only.
ip saddr 192.168.15.58 tcp dport 26 accept
# Explicitly reject every other source on the trusted port.
tcp dport 26 drop
# LAN/VPN administration. Replace or narrow to actual admin networks.
ip saddr 192.168.14.0/23 tcp dport { 22, 8006 } accept
# Quarantine reverse proxy. Replace with the real NPM source address.
ip saddr NPM_SOURCE_IP tcp dport 443 accept
# Zabbix agent. Verify the source address.
ip saddr 192.168.15.54 tcp dport 10050 accept
counter reject with icmpx type admin-prohibited
}
chain forward {
type filter hook forward priority 0;
policy drop;
}
chain output {
type filter hook output priority 0;
policy accept;
}
}
Important: If the perimeter router rewrites inbound SMTP source addresses to a LAN address instead of preserving the original Internet source, the rule above will block production mail. Verify source preservation with a packet capture before cutover.
Validate without applying:
nft -c -f /root/nftables-pmg.conf
Apply while using the local KVM console:
cp -a /etc/nftables.conf /etc/nftables.conf.pre-pmg 2>/dev/null || true
cp /root/nftables-pmg.conf /etc/nftables.conf
nft -f /etc/nftables.conf
nft list ruleset
systemctl restart nftables
Test:
- A LAN administrator can reach TCP 22 and 8006.
- Mailroom can reach TCP 26.
- An ordinary LAN workstation cannot reach TCP 26.
- An ordinary LAN workstation cannot submit mail through TCP 25.
- NPM can reach TCP 443 after the quarantine listener is configured.
- Zabbix can reach TCP 10050 after the agent is configured.
Never expose TCP 26 through the perimeter firewall or NPM.
11 Configure DNS for reputation and internal resolution[edit | edit source]
SpamAssassin and DNS blocklists work poorly through heavily shared public recursive resolvers. Cinzano included Unbound, so the recommended PMG design also uses a local recursive resolver.
11.1 Install Unbound[edit | edit source]
apt install unbound dnsutils
11.2 Forward the internal KitsNet zone[edit | edit source]
Create /etc/unbound/unbound.conf.d/kitsnet-internal.conf:
server:
interface: 127.0.0.1
interface: ::1
access-control: 127.0.0.0/8 allow
access-control: ::1 allow
hide-identity: yes
hide-version: yes
qname-minimisation: yes
forward-zone:
name: "lan.kitsnet.us."
forward-addr: INTERNAL_DNS_SERVER_1
forward-addr: INTERNAL_DNS_SERVER_2
Replace the DNS placeholders.
Validate and start:
unbound-checkconf
systemctl enable --now unbound
systemctl status unbound
Test:
dig @127.0.0.1 mailroom.lan.kitsnet.us A +short
dig @127.0.0.1 outbound.mailhop.org A +short
dig @127.0.0.1 proxmox.com A +short
If public recursion fails, verify that outbound UDP and TCP port 53 are permitted. Do not silently fall back to a public resolver until the DNSBL implications are understood.
11.3 Make PMG use Unbound[edit | edit source]
Configure PMG DNS as:
search kitsnet.us
nameserver 127.0.0.1
Then verify:
getent hosts mailroom.lan.kitsnet.us
getent hosts outbound.mailhop.org
Review PMG logs later for rules such as:
URIBL_BLOCKED RCVD_IN_DNSWL_BLOCKED SURBL_BLOCKED
Frequent appearances can indicate DNSBL query restrictions.
12 Install the NPM-issued certificate[edit | edit source]
The existing KitsNet certificate workflow can remain authoritative: NPM obtains the Let’s Encrypt certificate and the KitsNet export/import process deploys it to internal systems.
PMG uses separate certificates for:
| Purpose | File |
|---|---|
| PMG API and web interface | /etc/pmg/pmg-api.pem
|
| SMTP TLS | /etc/pmg/pmg-tls.pem
|
12.1 Initial manual import through the GUI[edit | edit source]
For the first installation, use the PMG certificate pages to upload:
- The unencrypted private key.
- The server certificate.
- The complete intermediate chain.
Install the mailgw.kitsnet.us certificate for both:
- API
- SMTP
After import:
pmgconfig cert info
openssl x509 -in /etc/pmg/pmg-api.pem -noout -subject -issuer -dates -fingerprint -sha256
openssl x509 -in /etc/pmg/pmg-tls.pem -noout -subject -issuer -dates -fingerprint -sha256
12.2 Adapt the existing certificate automation[edit | edit source]
Use the PMG certificate API or the installed pmgconfig cert interface rather than treating the certificate files as unmanaged generic files.
Before coding against the installed release:
pmgconfig cert set --help
pmgsh help /nodes/mailgw/certificates/custom -v
If the node/API name differs, inspect it with:
pmgsh get /nodes
The import automation must:
- Obtain the latest NPM private key and full certificate chain.
- Validate that the certificate contains
mailgw.kitsnet.us. - Validate that the private key matches the certificate.
- Install or update both API and SMTP certificates.
- Restart the affected PMG services.
- Verify the installed expiry date.
- Log success or failure to syslog/ConsoleWorks.
Pre-install validation can use:
openssl x509 -in fullchain.pem -noout -checkend 1209600
openssl x509 -in fullchain.pem -noout -ext subjectAltName
openssl pkey -in privkey.pem -pubout -outform pem | sha256sum
openssl x509 -in fullchain.pem -pubkey -noout | sha256sum
The two SHA-256 values must match.
13 Configure the PMG mail proxy[edit | edit source]
Open Configuration → Mail Proxy.
13.1 Relaying[edit | edit source]
Configure:
| Setting | Value |
|---|---|
| Default Relay | mailroom.lan.kitsnet.us
|
| Default Relay Port | 25
|
| Relay Protocol | SMTP
|
| Disable MX Lookup | Yes
|
| Smarthost | outbound.mailhop.org
|
| Smarthost Port | 2525
|
The Default Relay is used for accepted inbound mail. The Smarthost is used for outbound mail entering through the trusted internal listener.
13.2 Relay domains[edit | edit source]
For initial behavior compatibility, add the domains currently routed to mailroom:
kitsnet.us lan.kitsnet.us kitsnet.vancouver.bc.ca kitsnet.homelinux.org jeslacs.bc.ca lan.kitsnet.vancouver.bc.ca
The old transport file also contains ballantine.kitsnet.us. Do not copy it automatically. Add it only if current operational evidence shows that mail is still intentionally addressed to that domain.
PMG rejects inbound mail for destinations outside the relay-domain list.
13.3 Do not add broad trusted networks[edit | edit source]
Open Configuration → Mail Proxy → Networks.
Do not add the whole 192.168.14.0/23 LAN. PMG already treats the directly attached subnet as trusted, which is why the host firewall is required.
If the GUI permits adding a single host and it is useful for documentation, add only:
192.168.15.58/32 mailroom
The firewall remains the enforcement point.
13.4 Ports[edit | edit source]
Configure:
External/untrusted port: 25 Internal/trusted port: 26
13.5 Mail Proxy options[edit | edit source]
Set the following initial values:
| Option | Initial value | Reason |
|---|---|---|
| Greylisting IPv4 | Enabled | Reproduces SQLGrey behavior |
| Greylisting IPv6 | Disabled initially | No public IPv6 mail path has been documented |
| SPF | Enabled | Reproduces current SPF checking |
| RBL checks | Enabled | Reproduces current reputation checking |
| DNSBL sites | Include zen.spamhaus.org only if current Spamhaus terms permit this use
|
Reproduces current Postfix RBL |
| HELO tests | Disabled for the first clean-flow tests; enable before final acceptance | Avoids adding an unobserved rejection source during initial setup |
| Reject unknown sender domains | Enabled before final acceptance | Reproduces current sender-domain validation |
| Receiver verification | 550
|
Reproduces permanent rejection of nonexistent recipients |
| Hide Received | Enabled | Primary internal-route concealment mechanism |
| TLS | Enabled | SMTP TLS |
| Add TLS Received header | Enabled | Preserves useful TLS evidence in inbound headers |
| TLS logging | Enabled during migration; optional later | Troubleshooting |
| Log message headers | Enabled only if KitsNet accepts the privacy implications | Allows Tracking Center searches more like MailWatch subject searches |
| NDR on block | Disabled | Avoids backscatter and matches the current “Send Notices = no” posture |
| Before-queue filtering | Disabled | Retains after-queue quarantine behavior |
| Maximum message size | 133169152 bytes
|
Matches cinzano |
After configuration:
pmgsh get /config/mail
postconf myhostname
postconf message_size_limit
postconf relay_domains
postconf default_transport
ss -lntp | grep -E ':(25|26|8006)\b'
13.6 Receiver verification test[edit | edit source]
Before public cutover, test that mailroom answers recipient verification correctly:
swaks --server mailroom.lan.kitsnet.us --port 25 \
--from external-test@example.net \
--to known-user@kitsnet.us \
--quit-after RCPT
Repeat with a deliberately nonexistent recipient.
The valid recipient should be accepted. The nonexistent recipient should be rejected. If mailroom accepts every address, PMG receiver verification cannot protect the domain until mailroom is corrected.
14 Configure authenticated MailHop delivery[edit | edit source]
PMG supports a smarthost in the GUI, but SMTP username/password authentication is not exposed as a normal GUI setting. The persistent implementation uses the PMG Postfix template override system.
14.1 Install SASL client modules[edit | edit source]
apt update
apt install libsasl2-modules
14.2 Create the persistent template override[edit | edit source]
install -d -m 0755 /etc/pmg/templates
cp -a /var/lib/pmg/templates/main.cf.in /etc/pmg/templates/main.cf.in
cp -a /etc/pmg/templates/main.cf.in /root/main.cf.in.pre-mailhop
Never edit /var/lib/pmg/templates/main.cf.in directly.
Edit:
nano /etc/pmg/templates/main.cf.in
Append:
# KitsNet authenticated DuoCircle MailHop relay
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
# Require encrypted SMTP client sessions. Both mailroom and MailHop
# must pass the TLS tests before production cutover.
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
A global encrypt policy is suitable only after testing because PMG’s normal SMTP client destinations in this design are:
- Mailroom for inbound delivery.
- MailHop for outbound delivery.
The captured cinzano-to-mailroom message demonstrates SMTP TLS, but it must be retested from PMG. If either required destination cannot support verified TLS, use PMG’s destination-specific TLS policy rather than lowering all outbound connections to opportunistic TLS.
14.3 Create the credential map[edit | edit source]
install -o root -g root -m 0600 /dev/null /etc/postfix/sasl_passwd
nano /etc/postfix/sasl_passwd
Enter exactly:
[outbound.mailhop.org]:2525 MAILHOP_USERNAME:MAILHOP_PASSWORD
The bracketed hostname and port must exactly match the generated smarthost destination.
Build the map:
postmap /etc/postfix/sasl_passwd
chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
chmod 0600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
14.4 Generate and validate the effective Postfix configuration[edit | edit source]
pmgconfig sync --restart 1
postfix check
postconf default_transport
postconf smtp_sasl_auth_enable
postconf smtp_sasl_password_maps
postconf smtp_sasl_security_options
postconf smtp_sasl_tls_security_options
postconf smtp_tls_security_level
postconf smtp_tls_CAfile
Confirm the password-map lookup without displaying the password:
postmap -q '[outbound.mailhop.org]:2525' \
hash:/etc/postfix/sasl_passwd |
sed 's/:.*/:REDACTED/'
14.5 Verify network and TLS access[edit | edit source]
nc -vz outbound.mailhop.org 2525
openssl s_client \
-starttls smtp \
-connect outbound.mailhop.org:2525 \
-servername outbound.mailhop.org \
</dev/null
openssl s_client \
-starttls smtp \
-connect mailroom.lan.kitsnet.us:25 \
-servername mailroom.lan.kitsnet.us \
</dev/null
14.6 Upgrade maintenance requirement[edit | edit source]
After every PMG package upgrade:
- Compare the upstream and local
main.cf.intemplates. - Review upstream changes.
- Rebase the KitsNet additions onto the new template if necessary.
- Run
pmgconfig sync --restart 1. - Repeat the outbound MailHop test.
Example:
diff -u /var/lib/pmg/templates/main.cf.in \
/etc/pmg/templates/main.cf.in
15 Configure DKIM, SPF, and DMARC handling[edit | edit source]
15.1 Outbound DKIM[edit | edit source]
Leave PMG DKIM signing disabled:
Enable DKIM signing: No
MailHop signs the final outbound message. This avoids double signing and preserves the existing key-management arrangement.
15.2 Inbound DKIM[edit | edit source]
PMG verifies inbound DKIM signatures in the Spam Filter by default.
15.3 SPF[edit | edit source]
Enable PMG SPF checks.
The current public SPF record remains:
v=spf1 +a include:outbound.mailhop.org ~all
No SPF change is required if the public address and MailHop service remain unchanged.
15.4 DMARC[edit | edit source]
PMG/SpamAssassin evaluates DKIM and DMARC-related results as part of spam analysis, but the exact scoring and disposition can differ from the standalone OpenDMARC daemon used on cinzano.
The test plan must include:
- A known DMARC-pass message.
- A controlled SPF-fail message.
- A controlled DKIM-fail or unsigned message.
- Review of the PMG SpamAssassin rule hits.
- Adjustment of custom rule scores only after observed results.
Do not install a separate OpenDMARC stack during the initial migration.
16 Configure spam detection and quarantine[edit | edit source]
Open Configuration → Spam Detector.
16.1 Options[edit | edit source]
Recommended initial values:
| Option | Value |
|---|---|
| RBL checks | Enabled |
| Razor | Enabled |
| Bayes | Disabled initially |
| Attachment text extraction | Enabled |
| Languages | All |
| ClamAV heuristic score | Default initially |
Cinzano has a historical SQL Bayes database. Do not directly import it into PMG. PMG 9.1 uses current SpamAssassin 4 rules and a different platform. Start with the current rule set, compare results, and enable/train Bayes later only if it provides measurable benefit.
16.2 Reproduce the score-4 quarantine threshold[edit | edit source]
Open Configuration → Mail Filter and inspect the factory rules before changing them.
Create or modify a Spam Filter What-object:
Name: KitsNet Spam Score 4+ Type: Spam Filter Value: 4
Create or modify the inbound rule:
Name: KitsNet Quarantine Spam Direction: In What: KitsNet Spam Score 4+ Action: Quarantine Enabled: Yes
Ensure the rule priority places explicit blocklist and virus rules ahead of the general spam rule.
16.3 Preserve the high-spam classification[edit | edit source]
PMG records the actual spam score, so a score-7 message remains distinguishable even if the same quarantine action is used.
Optionally create a higher-priority What-object and rule:
Name: KitsNet High Spam 7+ Type: Spam Filter Value: 7 Rule: KitsNet Quarantine High Spam Direction: In Action: Quarantine Priority: higher than score-4 rule
Use the same quarantine disposition initially. Add special administrator notification only if it proves operationally useful.
16.4 Welcome- and blocklists[edit | edit source]
The existing MailWatch database contains whitelist and blacklist tables, but their live contents must be exported and reviewed.
On cinzano, export them before cutover:
mysql -B --raw -e 'SHOW CREATE TABLE mailscanner.whitelist\G' \
> /root/mailscanner-whitelist-schema.txt
mysql -B --raw -e 'SHOW CREATE TABLE mailscanner.blacklist\G' \
> /root/mailscanner-blacklist-schema.txt
mysql -B --raw -e 'SELECT * FROM mailscanner.whitelist' \
> /root/mailscanner-whitelist.tsv
mysql -B --raw -e 'SELECT * FROM mailscanner.blacklist' \
> /root/mailscanner-blacklist.tsv
Review every entry. Import active entries into PMG Who objects and the rule-based Welcome/Blocklist. Do not import stale appliance hostnames or obsolete test entries.
16.5 Custom SpamAssassin rules[edit | edit source]
The cinzano export did not show substantive active local rules in local.cf. Start with PMG defaults.
If custom rules are later required, place them in:
/etc/mail/spamassassin/custom.cf
Validate and restart:
spamassassin -D --lint
systemctl restart pmg-smtp-filter
17 Configure antivirus and malware handling[edit | edit source]
Open Configuration → Virus Detector.
17.1 Native PMG/ClamAV configuration[edit | edit source]
Confirm:
ClamAV: Enabled
PMG places identified virus messages in the administrator-controlled Virus Quarantine.
Recommended initial values:
| Setting | Value |
|---|---|
| Virus quarantine retention | 30 days |
| Encrypted archives treated as heuristic virus | Disabled initially, matching cinzano |
| Archive scan limits | PMG defaults initially |
| ClamAV signature updates | Enabled |
Verify:
systemctl status clamav-daemon
systemctl list-timers | grep -i clam
freshclam --version
17.2 Third-party ClamAV signatures: controlled parity item[edit | edit source]
Cinzano currently has clamav-unofficial-sigs configured with multiple third-party feeds, including SaneSecurity, URLhaus, Malware Patrol free data, SecuriteInfo free data, Linux Malware Detect, and others.
This is not a native PMG feature and is not part of the Proxmox-supported default installation.
Do not add these feeds before basic PMG filtering is validated. Instead:
- Establish a clean PMG/ClamAV baseline.
- Test the standard EICAR and dangerous-attachment cases.
- Record false-positive and false-negative behavior.
- Take a separate PMG and VM backup.
- Install and configure third-party signatures only in a second test phase.
- Enable feeds incrementally.
- Validate each feed’s licensing and current maintenance status.
- Retest benign Office, PDF, and archive mail.
- Document the exact files and update service.
- Add signature-age monitoring to Zabbix.
Production acceptance must explicitly state whether the migration uses:
- Native PMG/ClamAV equivalence, or
- Native PMG plus the third-party signature layer.
18 Configure dangerous attachment and archive policy[edit | edit source]
PMG supports relevant What objects and actions including:
- Match Filename
- Content Type Filter
- Archive Filter
- Match Archive Filename
- Remove Attachments
- Attachment Quarantine
- Block
- Notification
The existing eFa rules are extensive. Do not reduce them to a short extension list without review.
18.1 Build the dangerous filename What-object[edit | edit source]
Create a What-object named:
KitsNet Dangerous Filenames
Use Match Filename regular expressions representing the active cinzano deny rules, including at minimum:
\.(com|exe|scr|bat|cmd|cpl|pif|reg|chm|hta|js|jse|job|lnk|scf|sct|shb|shs|vbe|vbs|wsf|wsh|xnk)$
Also include the remaining currently denied extensions after reviewing on cinzano:
/etc/MailScanner/filename.rules.conf /etc/MailScanner/archives.filename.rules.conf
18.2 Build the dangerous content-type What-object[edit | edit source]
Create:
Name: KitsNet Dangerous Content Types Type: Content Type Filter
Match executable, self-extracting, ELF, and registry content as supported by PMG’s content-type selector.
18.3 Build archive objects[edit | edit source]
Create:
KitsNet Dangerous Archive Filenamesusing Match Archive Filename.KitsNet Dangerous Archive Contentusing Archive Filter.
These rules must detect dangerous content even when an apparently harmless ZIP filename is used.
18.4 Safe initial disposition[edit | edit source]
The closest safe analogue to “quarantine the whole message and do not deliver a cleaned message” is:
- Preserve the original message in Attachment Quarantine.
- Do not deliver the dangerous attachment.
- Use a final Block disposition for the processed message.
- Notify the administrator, not the apparent sender, unless a deliberate sender-notification policy is approved.
PMG’s Remove Attachments action can store the original in Attachment Quarantine. Combine it with a higher-priority or final Block rule only after testing the exact rule-processing order in the installed PMG release.
Do not let end users self-release messages identified as dangerous attachments.
18.5 Encrypted archives[edit | edit source]
Cinzano does not globally block encrypted messages. Preserve that initially.
Create a report-only or scoring test for encrypted archives during the pilot. After observing actual traffic, decide whether to:
- Continue allowing them.
- Increase the spam score.
- Quarantine them.
- Block them for external inbound mail only.
19 Investigate current TCP 587 use before cutover[edit | edit source]
Cinzano currently has a Postfix submission service on TCP 587 using Dovecot SASL. PMG is not intended to replace an authenticated end-user message-submission server.
Before migration, determine whether anything actually uses cinzano TCP 587:
grep -h 'postfix/submission/smtpd' /var/log/maillog* |
tail -n 500
ss -ntp | grep ':587'
Also inspect firewall and client configurations.
If no usage is found:
- Do not open TCP 587 on PMG.
- Remove the old exposure when cinzano is retired.
If active usage is found:
- Move authenticated client submission to mailroom.
- Do not add SMTP AUTH to PMG merely to preserve an obsolete gateway submission path.
- Test each affected client before retiring cinzano.
20 Configure internal-route concealment[edit | edit source]
20.1 Enable PMG Hide Received[edit | edit source]
In Mail Proxy Options:
Hide Received: Enabled
This is PMG’s native control for hiding internal Received: headers on outgoing mail.
20.2 Acceptance requirement[edit | edit source]
An outbound message received at iCloud, Gmail, or Outlook.com must not contain any of:
mailroom.lan.kitsnet.us ciroc.lan.kitsnet.us lafite.lan.kitsnet.us 192.168.15.58 192.168.15.127 any other internal workstation hostname any other internal private client address
It is acceptable and expected to retain:
- The PMG gateway identity.
- The public KitsNet address.
- MailHop headers.
- Destination-provider headers.
20.3 Do not copy the old header_checks immediately[edit | edit source]
Do not copy cinzano’s /etc/postfix/header_checks unchanged. It contains obsolete Ballantine references and was designed around MailScanner’s reinjection path.
First test PMG’s native Hide Received function.
If internal data still leaks:
- Preserve the complete raw received message.
- Preserve the PMG Tracking Center record and mail log.
- Identify the exact unwanted header.
- Implement an outbound-specific Postfix template correction.
- Retest outbound and inbound headers.
- Confirm that external-origin
Received:headers remain intact on inbound mail.
This is a production acceptance gate. Do not cut over all mail until it passes.
21 Configure PMG quarantine and user reports[edit | edit source]
Open Spam Detector → Quarantine.
Recommended initial configuration:
| Option | Value |
|---|---|
| Spam quarantine lifetime | 30 days |
| Virus quarantine lifetime | 30 days |
| Attachment quarantine lifetime | 30 days |
| Report style | Verbose |
| Authentication | Ticket initially |
| External images | On-demand |
| Hyperlinks | Plain text or disabled initially |
| Quarantine host | mailgw.kitsnet.us
|
| Quarantine protocol | https
|
| Quarantine port | 443
|
| Report From address | A valid KitsNet administrative address |
PMG sends a user report only when new messages exist in that user’s quarantine.
Messages addressed to KitsNet relay domains should be delivered directly to mailroom rather than consuming a MailHop external-send credit. Verify this during testing.
21.1 Future Samba AD quarantine login[edit | edit source]
Ticket login is simplest for the initial migration.
After production mail flow is stable, PMG can be integrated with Samba AD/LDAP and configured for combined LDAP/ticket authentication. This permits either:
- The report ticket, or
- The user’s directory credentials.
Do not make LDAP integration a prerequisite for initial mail-flow cutover.
22 Provide a quarantine-only HTTPS listener on PMG[edit | edit source]
The existing NPM proxy forwards to an internal HTTPS listener on port 443. Preserve that architecture by installing NGINX on PMG and exposing only quarantine paths.
22.1 Install NGINX[edit | edit source]
apt install nginx
rm -f /etc/nginx/sites-enabled/default
22.2 Create the PMG quarantine proxy[edit | edit source]
Create /etc/nginx/sites-available/pmg-quarantine.conf:
server {
listen 443 ssl;
server_name mailgw.kitsnet.us;
ssl_certificate /etc/pmg/pmg-api.pem;
ssl_certificate_key /etc/pmg/pmg-api.pem;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header PVEClientIP $remote_addr;
proxy_buffering off;
client_max_body_size 0;
proxy_connect_timeout 3600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
send_timeout 3600s;
location ~ /proxmoxlib.js$|/favicon.ico$|/pve2/|/fontawesome/|/framework7|/pwt|/mobile {
proxy_pass https:=127.0.0.1:8006;
}
location /quarantine {
proxy_pass https:=127.0.0.1:8006;
}
location /api2 {
location ~ /api2/(extjs|json|htmlmail)/(access/ticket$|version$) {
proxy_pass https:=127.0.0.1:8006;
}
location ~ /api2/(extjs|json|htmlmail)/nodes/.+/subscription$ {
proxy_pass https:=127.0.0.1:8006;
}
location ~ /api2/(extjs|json|htmlmail)/quarantine {
proxy_pass https:=127.0.0.1:8006;
}
return 403;
}
location / {
return 403;
}
}
Enable and test:
ln -s /etc/nginx/sites-available/pmg-quarantine.conf \
/etc/nginx/sites-enabled/pmg-quarantine.conf
nginx -t
systemctl enable --now nginx
Validate locally:
curl -kI https:=127.0.0.1/quarantine
curl -kI https:=127.0.0.1/
The quarantine path should be proxied. General administrative paths should return 403.
22.3 Configure NGINX Proxy Manager[edit | edit source]
During testing, create a temporary NPM proxy host such as:
pmg-test.kitsnet.us
Configure:
| NPM setting | Value |
|---|---|
| Scheme | HTTPS |
| Forward hostname/IP | 192.168.15.92 or a temporary internal PMG alias
|
| Forward port | 443
|
| Block Common Exploits | Enabled |
| Websocket Support | Enabled |
| Force SSL | Enabled |
| HTTP/2 | Enabled |
| HSTS | Disabled until testing is complete |
After final cutover, change the existing mailgw.kitsnet.us proxy host from the cinzano backend to PMG port 443.
Direct PMG administration remains:
https:=192.168.15.92:8006/
and is not published through NPM.
23 Configure PMG reports, graphs, and MailWatch analogues[edit | edit source]
PMG does not reproduce MailWatch’s page layout, but it provides equivalent core operational functions.
| MailWatch/eFa function | PMG analogue |
|---|---|
| Message search | Tracking Center |
| Message routing and disposition | Tracking Center expanded log |
| Spam score and rule hits | Tracking Center and Spam Quarantine details |
| Spam quarantine | Quarantine → Spam |
| Virus quarantine | Quarantine → Virus |
| Dangerous attachment quarantine | Quarantine → Attachment |
| Message queue | Administration → Mail Queue |
| Current mail-volume graph | Dashboard |
| Historical incoming/outgoing statistics | Statistics |
| Top internal senders | Statistics → Sender |
| Top internal receivers | Statistics → Receiver |
| External recipients of outbound mail | Statistics → Contact |
| Spam/virus proportions | Statistics → Incoming Mail |
| Processing-time information | Statistics and Tracking Center |
| Service status | Administration → Services |
| Daily administrator report | PMG daily report |
| Per-user quarantine report | PMG spam report |
23.1 Statistics retention[edit | edit source]
Set the PMG statistics lifetime to at least:
365 days
This provides annual graphs and comparisons. Observe PostgreSQL and disk growth during the first months.
Enable advanced statistics only after confirming that Sender/Receiver/Contact classification matches the desired MailWatch-style view.
23.2 Tracking Center retention[edit | edit source]
Tracking Center is based on local mail logs, not only the statistics database. PMG application backups do not include those logs.
Determine the default log rotation:
grep -R "rotate" /etc/logrotate.d /etc/logrotate.conf
ls -lh /var/log/mail* /var/log/syslog* 2>/dev/null
For KitsNet’s low mail volume, retain 30 to 90 days of compressed mail logs if disk capacity permits. Adjust only after identifying the exact PMG log files and logrotate stanza.
Continue forwarding logs to ConsoleWorks or another central log service for longer retention.
23.3 Daily report[edit | edit source]
Enable the PMG daily administrator report and send it to:
reroot@lan.kitsnet.us
Verify that mail for KitsNet relay domains is delivered directly to mailroom rather than through MailHop.
24 Configure Zabbix monitoring[edit | edit source]
Create a new Zabbix host:
Host name: mailgw.kitsnet.us Visible name: MailGW New Interface: 192.168.15.92:10050
Do not copy the obsolete Apache, PHP-FPM, or MariaDB templates from cinzano. PMG uses different services.
Recommended templates/checks:
- Linux by Zabbix agent 2.
- A local or external SMTP availability check for TCP 25 that does not create a trusted same-subnet relay path.
- PMG trusted SMTP listener TCP 26 tested from mailroom or by an agent-side local check.
- HTTPS on TCP 8006 from an internal monitoring point.
- HTTPS quarantine listener on TCP 443 from NPM or an approved monitoring path.
- Postfix queue depth.
- Disk space and inode use.
- CPU and memory.
- Certificate expiry for API and SMTP certificates.
- ClamAV database age.
- PMG service state.
- NGINX service state.
- Unbound service state.
Monitor these services:
postfix pmg-smtp-filter pmgpolicy pmgproxy pmgdaemon postgresql clamav-daemon nginx unbound nftables
Example service test:
for service in \
postfix \
pmg-smtp-filter \
pmgpolicy \
pmgproxy \
pmgdaemon \
postgresql \
clamav-daemon \
nginx \
unbound \
nftables
do
systemctl is-active "$service" || echo "$service failed"
done
25 Configure syslog and ConsoleWorks[edit | edit source]
Use rsyslog forwarding rather than modifying PMG application code.
Create a KitsNet-specific rsyslog configuration using the established ConsoleWorks destination and transport. Prefer TCP with a disk-assisted queue.
After configuration:
rsyslogd -N1
systemctl restart rsyslog
logger -t PMG-KitsNet-Test "PMG ConsoleWorks forwarding test"
Confirm the test message appears in ConsoleWorks.
Enable PMG’s optional message-header logging only after accepting the privacy implications. It can expose sender, recipient, and Subject information in logs.
26 Configure backups[edit | edit source]
PMG configuration backups include:
/etc/pmg- Mail-filter rules
- Statistics database
They do not include:
- Network setup
- Postfix queue contents
- Spam quarantine
- Virus quarantine
- Tracking Center mail logs
26.1 Create a PMG configuration backup[edit | edit source]
Use Administration → Backup/Restore or the installed command-line backup facility:
pmgbackup backup
ls -lh /var/lib/pmg/backup/
Copy the resulting backup off dusse.
26.2 Continue VM-level backup[edit | edit source]
Continue the existing wort/Veeam VM backup. VM-level backup protects quarantine, logs, network configuration, and customized packages that are not part of the PMG application backup.
26.3 Separately preserve local customizations[edit | edit source]
Back up:
/etc/pmg/templates/main.cf.in /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db /etc/nginx/sites-available/pmg-quarantine.conf /etc/unbound/unbound.conf.d/kitsnet-internal.conf /etc/nftables.conf /etc/rsyslog.d/ /etc/zabbix/ certificate deployment scripts
Protect the MailHop credential and private keys.
27 Controlled validation plan[edit | edit source]
Do not change production mail routing until all offline and direct tests pass.
27.1 Test phase 1: system health[edit | edit source]
Run:
pmgversion -v
systemctl --failed
postfix check
pmgconfig sync --restart 1
ss -lntp
Confirm these listeners:
25/tcp 26/tcp 443/tcp 8006/tcp
Confirm:
- Port 26 is reachable from mailroom.
- Port 26 is not reachable from an ordinary LAN workstation.
- Port 25 is not reachable from an ordinary LAN workstation under the final firewall policy.
- Port 8006 is reachable from the administrator LAN/VPN.
- Port 443 is reachable from NPM.
- General administrative paths through port 443 return 403.
27.2 Test phase 2: MailHop TLS and authentication[edit | edit source]
From PMG:
openssl s_client \
-starttls smtp \
-connect outbound.mailhop.org:2525 \
-servername outbound.mailhop.org \
</dev/null
Send a message into PMG port 26 from mailroom:
swaks \
--server 192.168.15.92 \
--port 26 \
--from psmode@kitsnet.us \
--to EXTERNAL_TEST_ADDRESS \
--header "Subject: PMG controlled outbound test" \
--body "Controlled PMG outbound test."
Watch:
journalctl -f \
-u postfix \
-u pmg-smtp-filter \
-u pmgpolicy
Confirm:
- PMG accepted the message on port 26.
- PMG classified and scanned it as outbound.
- PMG connected only to
outbound.mailhop.org:2525. - STARTTLS was used.
- SASL authentication succeeded.
- MailHop accepted the message.
- MailHop performed DKIM signing.
- SPF passed.
- DMARC aligned as expected.
- The message appears in PMG Tracking Center and Statistics.
27.3 Test phase 3: raw outbound headers[edit | edit source]
At the external recipient, capture the complete raw headers.
Search for internal information:
grep -Ei \
'mailroom|ciroc|lafite|lan\.kitsnet\.us|192\.168\.14\.|192\.168\.15\.' \
outbound-pmg-headers.txt
Expected result: no internal route information.
Confirm that the visible path ends at the PMG gateway and then MailHop.
27.4 Test phase 4: MailHop outage behavior[edit | edit source]
Temporarily block PMG’s connection to MailHop using a controlled firewall rule.
Submit one outbound test.
Confirm:
- The message remains deferred in the PMG queue.
- PMG does not fall back to direct recipient MX delivery.
- The message is delivered after MailHop connectivity is restored.
Remove the temporary block immediately.
27.5 Test phase 5: inbound without production NAT cutover[edit | edit source]
Create a temporary perimeter test mapping:
Public TCP 2526 -> 192.168.15.92 TCP 25
Restrict the mapping to a known external test source address when possible. The PMG firewall may need a temporary narrowly scoped exception if the test source is translated to a LAN address.
From an external system:
swaks \
--server 72.225.204.20 \
--port 2526 \
--from external-test@example.net \
--to VALID_USER@kitsnet.us \
--header "Subject: PMG controlled inbound test"
Confirm:
- PMG identifies the message as inbound.
- It performs SPF, DNSBL, greylist, spam, DKIM, and antivirus processing.
- It delivers clean mail to mailroom.
- It appears in Tracking Center.
- It appears in inbound Statistics.
- The received message preserves external source headers.
- The mailroom-delivery hop identifies PMG.
27.6 Test phase 6: invalid relay domain[edit | edit source]
Submit an inbound message through the external test mapping to:
test@example-not-relayed.invalid
PMG must reject the recipient and must not relay the message.
27.7 Test phase 7: invalid recipient[edit | edit source]
Submit to a nonexistent user at kitsnet.us.
PMG must reject the recipient with the configured 550 response after verification against mailroom.
27.8 Test phase 8: greylisting[edit | edit source]
Use a previously unseen sender/IP/recipient tuple.
Confirm:
- The first attempt receives a temporary 451 response.
- A later retry succeeds.
- Known sender behavior is not repeatedly delayed without cause.
27.9 Test phase 9: GTUBE spam[edit | edit source]
Send a controlled GTUBE test message from the external test system.
Expected:
- Spam score exceeds the high-spam threshold.
- Message is placed in Spam Quarantine.
- User report includes it.
- It is visible in Tracking Center with a quarantine disposition.
27.10 Test phase 10: EICAR antivirus[edit | edit source]
Send the standard EICAR test string in a controlled attachment.
Expected:
- Virus detected.
- Message placed in Virus Quarantine.
- Message not delivered to the user.
- Administrator can inspect the quarantine record.
Do not use live malware.
27.11 Test phase 11: attachment policy[edit | edit source]
Test each of:
- Ordinary PDF.
- Ordinary Office document.
- Ordinary ZIP.
.exerenamed with a benign filename.- Executable inside ZIP.
- Script inside ZIP.
- Password-protected ZIP.
- Double-extension filename.
- Very long attachment filename.
- Benign registry-related file if operationally relevant.
Record expected and actual disposition in a test table.
27.12 Test phase 12: message size[edit | edit source]
Test:
- A message comfortably below 133169152 bytes.
- A message above the actual MailHop provider limit.
- A message above PMG’s configured limit.
Document the effective end-to-end maximum. The smallest limit among Outlook, mailroom, PMG, MailHop, and the destination provider controls real behavior.
27.13 Test phase 13: mailroom outage[edit | edit source]
Temporarily stop or firewall mailroom SMTP.
Send inbound mail to PMG through the test mapping.
Confirm:
- PMG queues the accepted message according to the designed flow.
- The queue shows deferred delivery.
- Mail is delivered after mailroom returns.
- No message is lost.
27.14 Test phase 14: reports and quarantine URL[edit | edit source]
Force a spam quarantine entry and generate or wait for a report.
Confirm:
- Report arrives in the KitsNet mailbox.
- Link uses
https:=mailgw.kitsnet.us/and port 443. - NPM forwards to PMG.
- User can log in with the ticket.
- The PMG administrative GUI is not exposed through the public quarantine URL.
- Images are not fetched automatically.
27.15 Test record template[edit | edit source]
Use a table like this for every test:
| Test ID | Date/time | Sender | Recipient | Expected result | Actual result | PMG queue/tracking ID | Pass/fail | Notes |
|---|---|---|---|---|---|---|---|---|
| T-001 |
28 Production migration plan[edit | edit source]
The migration is intentionally split into outbound and inbound cutovers.
28.1 Pre-cutover checklist[edit | edit source]
Before changing production routing:
- All controlled tests pass.
- PMG backup exists off dusse.
- Dusse VM backup exists through wort/Veeam.
- Cinzano remains powered on and unchanged.
- MailHop authentication has been proven.
- Internal-header concealment has been proven using raw headers.
- Recipient verification has been proven.
- Spam, virus, and dangerous-attachment tests have passed.
- NPM quarantine path has been proven.
- Zabbix and ConsoleWorks monitoring are active.
- Rollback commands and old values are recorded.
- PMG and cinzano queues are understood and healthy.
- Any current use of cinzano TCP 587 has been resolved.
28.2 Outbound cutover first[edit | edit source]
Inbound Internet mail continues through cinzano.
On mailroom, record the existing relay:
postconf relayhost
postconf -n > /root/postconf-before-pmg-outbound.txt
cp -a /etc/postfix/main.cf /etc/postfix/main.cf.before-pmg-outbound
Change mailroom’s outbound relay to:
[192.168.15.92]:26
Using the IP during pilot avoids ambiguity with the public mailgw.kitsnet.us name. A dedicated internal alias may be substituted after it is deliberately created and tested.
Use mailroom’s normal configuration mechanism rather than editing a generated file.
Validate:
postfix check
systemctl reload postfix
postconf relayhost
Monitor continuously for the first hour:
mailq
journalctl -f -u postfix
Monitor PMG:
- Tracking Center
- Mail Queue
- Statistics → Sender
- Statistics → Contact
- System logs
Monitor MailHop:
- Message Log
- Authentication errors
- Rejections
- DKIM signing
- Sender-domain acceptance
Run Outlook, application, and system-generated outbound tests.
Keep outbound on PMG for at least 48 to 72 hours before inbound cutover.
28.3 Outbound rollback[edit | edit source]
If a material problem occurs:
- Restore mailroom’s old relay to cinzano.
- Run
postfix check. - Reload Postfix.
- Confirm new messages reach cinzano.
- Leave PMG’s queue intact until every queued message is accounted for.
- Diagnose without changing inbound production flow.
28.4 Inbound cutover[edit | edit source]
Because the public MX and public address remain unchanged, cutover is performed by changing the perimeter NAT destination.
Immediately before cutover:
# On cinzano
mailq
# On PMG
mailq
systemctl --failed
Change:
Public 72.225.204.20 TCP 25 -> 192.168.15.83 TCP 25
to:
Public 72.225.204.20 TCP 25 -> 192.168.15.92 TCP 25
Do not shut down cinzano.
Immediately test from at least two external providers.
Confirm:
- NAT reaches PMG and preserves the Internet source address.
- PMG accepts only configured domains.
- Valid recipients are accepted.
- Invalid recipients are rejected.
- Clean mail reaches mailroom.
- External DKIM/SPF/DMARC results are analyzed.
- Tracking Center has complete records.
- Zabbix detects the active SMTP service.
28.5 Web/quarantine cutover[edit | edit source]
Change the existing NPM proxy host for mailgw.kitsnet.us to forward to:
https:=192.168.15.92:443
Keep:
- Force SSL enabled.
- HTTP/2 enabled.
- WebSocket support enabled.
- HSTS disabled until stable.
After several stable days, HSTS may be enabled.
28.6 Inbound rollback[edit | edit source]
If a material problem occurs:
- Restore the perimeter TCP 25 NAT destination to 192.168.15.83.
- Confirm new inbound connections reach cinzano.
- Keep the PMG queue intact and account for every accepted message.
- Restore the NPM backend if quarantine access is affected.
- Leave mailroom outbound through PMG if outbound operation is healthy, or restore it separately if required.
Public DNS normally does not need to be changed during rollback.
29 Parallel observation period[edit | edit source]
Keep cinzano available for at least 30 days after both cutovers.
During this period:
- Do not delete its quarantine.
- Do not delete its MailWatch database.
- Do not remove its configuration backup.
- Compare spam scores and false positives.
- Compare dangerous-attachment handling.
- Review PMG Spam, Virus, and Attachment quarantines daily.
- Review PMG queue and deferred deliveries.
- Review MailHop rejections.
- Verify Outlook and application-generated raw headers.
- Verify certificate renewal automation.
- Verify statistics and report retention.
- Verify Zabbix and ConsoleWorks alerts.
- Confirm no system still uses cinzano TCP 587.
- Confirm no system still submits outbound mail directly to cinzano.
30 Final acceptance checklist[edit | edit source]
[ ] dusse is confirmed to run Proxmox Mail Gateway, not Proxmox VE [ ] PMG is fully updated on the current supported release [ ] PMG FQDN and SMTP identity are correct [ ] PMG IP remains 192.168.15.92 [ ] Public TCP 25 reaches PMG after cutover [ ] PMG external port is 25 [ ] PMG internal trusted port is 26 [ ] General LAN hosts cannot relay through PMG port 25 or 26 [ ] Host firewall permits port 26 only from mailroom [ ] PMG accepts only approved relay domains [ ] Receiver verification rejects nonexistent users [ ] Clean inbound mail reaches mailroom [ ] Greylisting works without unacceptable delays [ ] SPF checking is enabled [ ] RBL checking works without DNSBL quota errors [ ] DKIM verification works inbound [ ] DMARC-related SpamAssassin results have been reviewed [ ] Spam score 4 and above is quarantined [ ] High-scoring spam remains distinguishable [ ] ClamAV EICAR test succeeds [ ] Third-party ClamAV signature decision is documented [ ] Dangerous attachments are administrator-controlled [ ] Executables inside archives are detected [ ] Message-size behavior is documented [ ] Outbound mail enters PMG on port 26 [ ] Outbound mail is scanned [ ] PMG authenticates to outbound.mailhop.org:2525 [ ] MailHop connection uses TLS [ ] PMG does not fall back to direct recipient MX delivery [ ] MailHop DKIM signing passes [ ] SPF and DMARC alignment pass for production domains [ ] External raw headers contain no mailroom/client/private routing data [ ] Public quarantine URL uses mailgw.kitsnet.us [ ] Public quarantine proxy does not expose PMG administration [ ] PMG Tracking Center is usable [ ] PMG statistics retain the required history [ ] PMG daily report arrives [ ] User quarantine reports arrive [ ] Zabbix monitors PMG and its dependencies [ ] ConsoleWorks receives PMG syslog [ ] PMG application backup succeeds [ ] Dusse VM backup succeeds [ ] Certificate renewal/import automation succeeds [ ] Outbound rollback is documented and tested [ ] Inbound NAT rollback is documented and tested [ ] No production system still depends on cinzano port 587 [ ] Cinzano queues are empty before decommission
31 Decommissioning cinzano[edit | edit source]
Do not decommission cinzano until the observation period and final acceptance checklist are complete.
Then:
- Take a final cinzano VM backup.
- Export the final MailWatch database.
- Export the final quarantine inventory.
- Save the final Postfix, MailScanner, SpamAssassin, ClamAV, OpenDKIM, OpenDMARC, SQLGrey, firewall, and web configuration.
- Confirm both cinzano and PMG queues are empty.
- Remove cinzano from active monitoring only after PMG monitoring is stable.
- Remove obsolete perimeter and internal firewall rules.
- Remove obsolete port 587 exposure if unused.
- Retain the final image and documentation according to KitsNet retention policy.
- Rotate the old MailHop password and any other credentials present in exported configuration archives.
- Update KitsNet operational documentation to identify dusse/PMG as the active mail gateway.
32 Ongoing operating procedure[edit | edit source]
32.1 Daily[edit | edit source]
- Review PMG Dashboard.
- Review deferred Mail Queue entries.
- Review Virus and Attachment quarantines.
- Review Zabbix and ConsoleWorks alerts.
32.2 Weekly[edit | edit source]
- Review Spam Quarantine false positives.
- Review Tracking Center for unusual rejections or delivery failures.
- Review MailHop rejection and sender-domain logs.
- Verify ClamAV signature age.
- Verify disk usage and log growth.
32.3 Monthly[edit | edit source]
- Apply PMG updates.
- Compare the upstream and local
main.cf.intemplates after upgrades. - Run an outbound raw-header concealment test.
- Verify certificate expiry.
- Verify PMG and VM backups.
- Review relay domains, welcome lists, blocklists, and dangerous-attachment rules.
- Review whether any third-party ClamAV feed causes false positives.
32.4 After every certificate renewal[edit | edit source]
- Verify the API certificate.
- Verify the SMTP certificate.
- Verify NGINX quarantine access.
- Verify NPM external access.
- Check certificate expiry through Zabbix.
32.5 After every PMG upgrade[edit | edit source]
pmgversion -v
diff -u /var/lib/pmg/templates/main.cf.in \
/etc/pmg/templates/main.cf.in
pmgconfig sync --restart 1
postfix check
systemctl --failed
Then send:
- One outbound test through MailHop.
- One inbound test.
- One spam-quarantine test.
- One raw-header concealment test.
33 References[edit | edit source]
- [https:=pmg.proxmox.com/pmg-docs/ Proxmox Mail Gateway current documentation index]
- [https:=pmg.proxmox.com/pmg-docs/pmg-admin-guide.html Proxmox Mail Gateway Administration Guide]
- [https:=pmg.proxmox.com/pmg-docs/chapter-pmgconfig.html PMG Configuration Management]
- [https:=pmg.proxmox.com/pmg-docs/pmgconfig.1.html pmgconfig command reference]
- [https:=pmg.proxmox.com/pmg-docs/pmg.conf.5.html pmg.conf reference]
- [https:=pmg.proxmox.com/pmg-docs/pmgbackup.1.html PMG backup and restore reference]
- [https:=pmg.proxmox.com/wiki/Getting_started_with_Proxmox_Mail_Gateway Getting Started with Proxmox Mail Gateway]
- [https:=pmg.proxmox.com/wiki/Quarantine_Web_Interface_Via_Nginx_Proxy Quarantine Web Interface via NGINX Proxy]
- [https:=pmg.proxmox.com/wiki/Change_FQDN Changing the PMG FQDN]
- [https:=pmg.proxmox.com/wiki/DNS_server_on_Proxmox_Mail_Gateway DNS server on PMG]
- [https:=www.postfix.org/SASL_README.html Postfix SASL client documentation]
34 Source material reviewed for the KitsNet-specific design[edit | edit source]
cinzano.pdfballantine.pdf- Cinzano Postfix and MailScanner configuration export
- Cinzano system and firewall export
- Cinzano raw inbound and outbound message headers
- Cinzano end-to-end mail log trace
- DuoCircle MailHop sending-domain screenshot
- NGINX Proxy Manager screenshots
- Zabbix host configuration screenshot
kitsnet_us-dns.pdf
The raw outbound capture is the authoritative baseline for internal-route concealment. The PMG replacement is not accepted until it produces equivalent privacy at the external recipient.