Nginx TCP Forwarding for Subdomain – Configuration Guide

debianemail-servernginxnginx-reverse-proxysubdomain

I have Debian 12 box on Hetzner running Cowmail and Nginx.
Cowmail runs under mail.mydomain.com subdomain (mydomain.com is placeholder) on ports 8069 and 44369.

What I am trying to do is:

  1. Serve static html when visiting mydomain.com (served by nginx).
  2. Redirect ports 443, 80 to 44369, 8069 respectively when vising mail subdomain.

Currently when I try to vising mydomain.com it still redirects ports.

/etc/nginx.conf (Comments removed, the only diffrence from default file is include at the end)

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
}

http {
        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dr>
        ssl_prefer_server_ciphers on;
        access_log /var/log/nginx/access.log;
        gzip on;
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

include /etc/nginx/tcpconf.d/*;

/etc/nginx/tcpconf.d/lb

stream {
    upstream web_server {
        server mail.mydomain.com:44369;
    }

    server {
        listen 443;
        proxy_pass web_server;
    }
}

/etc/nginx/sites-avalible/default (comments removed, simlink in /etc/nginx/sites-enabled/)

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /home/www-none/public;
    index index.html ;

    server_name _;
    location / {
        try_files $uri $uri/ =404;
    }
}

server {
    server_name www.mydomain.com;
    return 301 $scheme://mydomain.com$request_uri;
}

server {
    root /home/www-mydomain-com/public; 
    server_name mydomain.com;

    ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        try_files $uri $uri/ =404;
    }
}

Additional notes:

  • Server will host multiple domains, so that's why it will return page from /home/www-none/public when requested directly by IP.
  • SSL certificates generated by Let's Encrypt using certbot.
  • Certificates for mail.mydomain.com and mydomain.com are diffrent.
  • mydomain.com website is stored in /home/www-mydomain-com/public, Nginx DOES have access to it, because user www-data has been added to www-mydomain.com and www-none groups.
  • I am using this TCP stream method, because when using 'classic' reverse proxy nginx replaces ssl certificate with one create for mydomain.com, which IS DIFFRENT than mail.mydomain.com. And cowmail has it's own certificate. Nginx should work like a bridge here.

EDIT:
Based on what djdomi suggested here what I have changed.
After removing include /etc/nginx/tcpconf.d/*; and appeding following code to /etc/sites-avalible/default. mydomain.com can be accessed but mail.mydomain.com have incorrect ssl certificate. Nginx shouldn't terminate SSL here.

server {
    server_name mail.mydomain.com;

    location  / {
        rewrite /(.*) /$1  break;
        proxy_pass         https://localhost:44369;
        proxy_redirect     off;
        proxy_set_header   Host $host;
    }
}

EDIT:
Replaced /etc/nginx/tcpconf.d/lb with following contents:

stream  {
    map $ssl_preread_server_name $internalport {
        mail.mydomain.com 44369;
        default glance-no-upstream-instance;
    }

    server {
        listen 443;
        ssl_preread on;
        proxy_connect_timeout 20s;
        proxy_timeout 30s;
        proxy_pass 127.0.0.1:$internalport;
    }
}

Now when I try to visit mydomain.com I get "PR_END_OF_FILE_ERROR" in Firefox.
And when I vising mail.mydomain.com I actually get into my email.

Best Answer

Virtual hosting is a concept of the HTTP 1.1 protocol. The TCP protocol is not aware of the hostname, as it is communicated via the Host: header in HTTP, or Server Name Indication (SNI) in HTTPS (TLS). Therefore, TCP proxies cannot be configured per hostname. They work on different layers.

But because you are proxying (not redirecting; that's a entirely different concept of the HTTP protocol) ports 443 (HTTPS) & 80 (HTTP) it strongly suggests that it would be a HTTP reverse proxy you need, instead. Yes, it is ok to let Nginx handle the TLS termination even in the case the back-end connection is also TLS encrypted. With the SNI you can have multiple different certificates server by the same Nginx instance.