We also implemented Nginx and Fail2ban now for extra security and able to use the portal for when 8006 in not allowed: these are our notes of it:
We run the let encypt option in pmg and use those in the config.
#----------------------------------------------------------
Nginx Website Port 443: (8006):
#----------------------------------------------------------
apt update
apt install nginx
#----------------------------------------------------------
/etc/nginx/sites-available/pmg
#########################
# Rate-limit zone for login
#########################
limit_req_zone $binary_remote_addr zone=pmg_login:10m rate=6r/m;
#########################
# HTTP ? HTTPS redirect
# Exclude PMG ACME HTTP validation
#########################
server {
listen 80;
server_name FQDN;
# ACME HTTP validation for Let's Encrypt
location /.well-known/acme-challenge/ {
proxy_pass
http://127.0.0.1:80
proxy_set_header Host $host;
}
# Redirect all other HTTP traffic to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
################################
# HTTPS Reverse Proxy for PMG
################################
server {
listen 443 ssl;
server_name FQDN;
# Enable HTTP/2
http2 on;
# Dedicated access log
access_log /var/log/nginx/pmg-access.log;
# PMG certificate (combined cert + key)
ssl_certificate /etc/pmg/pmg-api.pem;
ssl_certificate_key /etc/pmg/pmg-api.pem;
#######################################
# TLS
#######################################
ssl_protocols TLSv1.2 TLSv1.3;
# TLS 1.2 explicit ciphers (the 4 you requested)
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
# Recommended ECDH curves
ssl_ecdh_curve X25519:secp521r1:secp384r1:secp256r1;
# TLS 1.3 ciphers (OpenSSL 3.5.4 handles automatically)
# TLS_AES_256_GCM_SHA384
# TLS_CHACHA20_POLY1305_SHA256
# TLS_AES_128_GCM_SHA256
# OCSP stapling disabled (PMG cert has no responder)
# ssl_stapling on;
# ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;
# Session cache
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1h;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer always;
#######################################
# REVERSE PROXY TO PMG
#######################################
location / {
proxy_pass
https://127.0.0.1:8006
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
# WebSocket support for PMG UI
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Buffers for PMG UI
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_max_temp_file_size 0;
}
#######################################
# BRUTE-FORCE PROTECTION: LOGIN ENDPOINTS
#######################################
location = /api2/json/access/ticket {
limit_req zone=pmg_login burst=3 nodelay;
proxy_pass
https://127.0.0.1:8006
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
location = /api2/extjs/access/ticket {
limit_req zone=pmg_login burst=3 nodelay;
proxy_pass
https://127.0.0.1:8006
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
#----------------------------------------------------------
rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/pmg /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx
systemctl reload nginx
#----------------------------------------------------------
Fail2Ban:
#----------------------------------------------------------
apt install fail2ban
systemctl enable fail2ban
systemctl start fail2ban
systemctl status fail2ban
#----------------------------------------------------------
/etc/fail2ban/filter.d/pmg-login.conf
[Definition]
# Match failed login requests to PMG webgui (ticket API)
failregex = ^<HOST> - - \[.*\] "(POST|GET) /api2/(json|extjs)/access/ticket HTTP/[\d.]+" 200 ([5-9][0-9]|100) .*
ignoreregex =
/etc/fail2ban/jail.d/pmg-login.local
[pmg-login]
enabled = true
filter = pmg-login
action = nftables-multiport[name=PMGLogin, port=443, protocol=tcp]
logpath = /var/log/nginx/pmg-access.log
maxretry = 5
findtime = 600
bantime = 3600
/etc/fail2ban/jail.d/postfix-submission.local
[postfix-submission-auth]
enabled = true
filter = postfix[mode=auth]
action = nftables-multiport[name=PostfixSubmissionAuth, port="587", protocol=tcp]
logpath = /var/log/mail.log
maxretry = 5
findtime = 600 ; 10 minutes
bantime = 3600 ; 1 hour
/etc/fail2ban/jail.d/recidive.local
[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/8
bantime = 3600
findtime = 600
maxretry = 5
backend = auto