Skip to content

Instantly share code, notes, and snippets.

@h0tw1r3
Created September 4, 2025 20:00
Show Gist options
  • Save h0tw1r3/55ef442e469443a9c45c63e86a1a5ec0 to your computer and use it in GitHub Desktop.
Save h0tw1r3/55ef442e469443a9c45c63e86a1a5ec0 to your computer and use it in GitHub Desktop.
nginx mail proxy in front of mta submission server (no auth) with STARTTLS required
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log info;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
env RELAY_HOST;
env RELAY_PORT;
events {
worker_connections 1024;
}
http {
access_log /var/log/nginx/access.log;
log_not_found off;
perl_modules /etc/nginx/perl/lib;
perl_require auth.pm;
server {
listen 80 default_server;
listen [::]:80 ipv6only=on default_server;
server_name _;
root /dev/null; # No static content served
location /auth {
perl auth::handler;
}
}
}
mail {
server_name mail.example.com;
auth_http 127.0.0.1:80/auth;
smtp_capabilities PIPELINING 8BITMIME ENHANCEDSTATUSCODES "SIZE 10240000" SMTPUTF8 CHUNKING ETRN VRFY;
xclient on;
proxy_pass_error_message on;
ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.nopass.key;
server {
listen 25;
listen 465 ssl;
protocol smtp;
smtp_auth none;
starttls on;
}
server {
listen 587;
protocol smtp;
smtp_auth none;
starttls only;
}
}
package auth;
use strict;
use warnings;
use Socket;
use nginx;
sub handler {
my $r = shift;
# Require STARTTLS if Auth-Method is "none"
# nginx handles ensuring TLS for other methods (e.g. "plain", "login", "cram-md5")
if ($r->header_in("Auth-Method") eq "none" && $r->header_in("Auth-SSL") || "" ne "on") {
my $max_attempts = 3;
my $attempt = ($r->header_in("Auth-Login-Attempt") || 0) + 0;
if ($attempt > $max_attempts) {
$r->header_out("Auth-Status", "Too many errors");
$r->header_out("Auth-Error-Code", "503");
} else {
$r->header_out("Auth-Status", "Must issue a STARTTLS command first");
$r->header_out("Auth-Error-Code", "530 5.7.0");
$r->header_out("Auth-Wait", "1");
}
return DECLINED;
}
if (exists $ENV{RELAY_HOST} && $ENV{RELAY_PORT}) {
my $address = inet_ntoa(inet_aton($ENV{RELAY_HOST}));
$r->header_out("Auth-Status", "OK");
$r->header_out("Auth-Server", $address);
$r->header_out("Auth-Port", $ENV{RELAY_PORT});
return OK;
}
return DECLINED;
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment