nginx-proxy-manager icon indicating copy to clipboard operation
nginx-proxy-manager copied to clipboard

SSL Passthrough

Open ctops1 opened this issue 4 years ago • 11 comments

Is your feature request related to a problem? Please describe. Unable to pass through encrypted SSL connections to a server that doesn't need SSL encryption

Describe the solution you'd like A clear option or configuration to enable SSL passthrough via stream or similar method.

Describe alternatives you've considered Attempted to add code from links under additional context, host simply goes offline.

Additional context This has been discussed in some other unclosed issues linked at the bottom, but this should be possible with modern NGINX using the stream function. I see that NPM has "Stream" but it only allows you to capture an entire port instead of a specified domain for forwarding to another internal host.

https://serversforhackers.com/c/tcp-load-balancing-with-nginx-ssl-pass-thru https://stackoverflow.com/questions/38371840/ssl-pass-through-in-nginx-reverse-proxy

https://github.com/jc21/nginx-proxy-manager/issues/235 https://github.com/jc21/nginx-proxy-manager/issues/461

ctops1 avatar Jan 31 '21 22:01 ctops1

I have to say this would be a big feature. This would make dealing with cooler larger projects a breeze. Matrix/synapse comes to mind which is a pain to setup behind a separate proxy. It would be nice if you could just forward a specific domain 80 and 443 traffic to a service on a separate machine that does the ssl stuff.

ma-karai avatar Feb 23 '21 10:02 ma-karai

After spending a lot of time getting this to work in NGINX itself - I realize this request may not fulfill everyone's needs even if it was implemented according to what NGINX supports.

NGINX can do SSL passthru, but it can't do it on the same ports being serviced for normal NGINX services. You can do only one or the other.

For example, let's say you have some sites on 443 using normal NGINX SSL. You cannot then also use NGINX to perform passthrough to a VM that has its own certificates. You would have to use a different port, such as 8443 or a custom port. The only other alternative would be another box and another public IP.

I finally resolved my issue by switching back to pure NGINX and implementing passthru on 8443 and the rest of our sites on 443.

NPM is a great tool but it has its limitations - perhaps someday I will revisit it and I will still recommend it to those who have less complex setups than I.

ctops1 avatar Feb 25 '21 03:02 ctops1

for ssl pasthrough you need to use stream (npm now support it), but i think you can't use stream and http on the same port.

centralhardware avatar Mar 11 '21 04:03 centralhardware

SSL passthrough in nginx is complicated. So the only way to have ssl passthrough is by using a stream host. But in a stream host nginx has no idea what protocol is used by the data. Nginx can assume it is HTTP, but since it is HTTPS the data is encrypted, so even if nginx knows it is HTTP, he still does not now where to send this packet, as the target domain is encrypted as well. But there is a relatively new feature in nginx, which leverages Server Name Indication in TLS, where the domain is sent unencrypted in the HTTPS request. But this means the requesting client also has to send this SNI. But most modern browsers do, so let's just assume it is sent.

Here's the next problem: nginx cant run a stream on the same port as other proxies. It's one stream per port, and a port with a stream attached can't have any other proxies attached. We can work around this by routing all HTTPS traffic through this single stream, which in turn forwards it either to the ssl passthrough server or back to nginx itself on a different port, which can now handle all normal proxies. As you can imagine this will come with a performance penalty of all other proxies being proxied twice, and it might become a bottleneck.

That's why I have implemented it in this specific way: SSL Passthough is a new type of host which is strictly opt-in. Once you have opted in, all traffic is routed through the single stream as described above, but you will be able to add ssl passthrough hosts for a specific domain.

Remeber: Enabling SSL passthrough might slow down other proxied services and might break access for devices / browsers which do not send the SNI information!

chaptergy avatar Oct 12 '21 13:10 chaptergy

I was looking for this feature as well. Since the above PR #1479 doesn't seem to have been merged, I baked my own quick fix. It's not pretty but seems to do the trick (not tested thoroughly yet). Sharing here as it might help others.

Note that this approach makes designates an new port as the main input port for your NPM https traffic! This worked for me because I am behind NAT and could simply adjust the port forwarding for the public 443 port to a new port, and leave all the remaining NPM configuratio untouched. You might have to find another way if your NPM is directly listening on a public IP. This also comes with the caveats mentioned by @chaptergy regarding dual-proxying.

So here goes:

Add the following to your custom stream.conf (see https://nginxproxymanager.com/advanced-config/#custom-nginx-configurations) to passthrough SSL traffic for backend.example.com to the host 192.168.0.1:443 and backend2.example.com to 192.168.0.3:443:

map $ssl_preread_server_name $name {
    backend.example.com     backend;
    backend2.example.com    backend2;
    default                 npm;
}

upstream backend {
    server 192.168.0.1:443;
}

upstream backend2 {
    server 192.168.0.3:443;
}

upstream npm {
    server localhost:443;
}


server {
    listen      12346;
    proxy_pass  $name;
    ssl_preread on;
}

This is more or less a straight copy from the nginx docs example (https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html) with the addition of the npm -> localhost:443 default backend. Just add additional hostnames to the mapping and backends as required.

You might still need http proxy hosts on port 80 to make letsencrypt challenges work to those passed-through hosts.

Related issues: #776 #286

gabuzi avatar May 28 '23 23:05 gabuzi

Issue is now considered stale. If you want to keep it open, please comment :+1:

github-actions[bot] avatar Mar 20 '24 01:03 github-actions[bot]

I was looking for this feature as well. Since the above PR #1479 doesn't seem to have been merged, I baked my own quick fix. It's not pretty but seems to do the trick (not tested thoroughly yet). Sharing here as it might help others.

Note that this approach makes designates an new port as the main input port for your NPM https traffic! This worked for me because I am behind NAT and could simply adjust the port forwarding for the public 443 port to a new port, and leave all the remaining NPM configuratio untouched. You might have to find another way if your NPM is directly listening on a public IP. This also comes with the caveats mentioned by @chaptergy regarding dual-proxying.

So here goes:

Add the following to your custom stream.conf (see https://nginxproxymanager.com/advanced-config/#custom-nginx-configurations) to passthrough SSL traffic for backend.example.com to the host 192.168.0.1:443 and backend2.example.com to 192.168.0.3:443:

map $ssl_preread_server_name $name {
    backend.example.com     backend;
    backend2.example.com    backend2;
    default                 npm;
}

upstream backend {
    server 192.168.0.1:443;
}

upstream backend2 {
    server 192.168.0.3:443;
}

upstream npm {
    server localhost:443;
}


server {
    listen      12346;
    proxy_pass  $name;
    ssl_preread on;
}

This is more or less a straight copy from the nginx docs example (https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html) with the addition of the npm -> localhost:443 default backend. Just add additional hostnames to the mapping and backends as required.

You might still need http proxy hosts on port 80 to make letsencrypt challenges work to those passed-through hosts.

Related issues: #776 #286

Does this work for anyone else? I made these changes, got rid of backend2 (I only have one host I need to do SNI passthrough for) but all it did was break all my existing hosts and it never started listening on 12346.

jdkruzr avatar Mar 22 '24 19:03 jdkruzr

Does this work for anyone else? I made these changes, got rid of backend2 (I only have one host I need to do SNI passthrough for) but all it did was break all my existing hosts and it never started listening on 12346.

Still working for me with 2.11.1.

But I noticed a rather severe caveat for security: This extra layer of proxying breaks access list functionality as for the npm backend now all requests appear to come from the localhost, such that IP filtering is not possible! nginx proxy protocol would help here to retain the original IP, but it's not available for npm, see https://github.com/NginxProxyManager/nginx-proxy-manager/issues/1114

gabuzi avatar Mar 31 '24 15:03 gabuzi

Issue is now considered stale. If you want to keep it open, please comment :+1:

github-actions[bot] avatar Dec 15 '24 02:12 github-actions[bot]

Hi @gabuzi, thanks for sharing the solution. But I couldn't get this solution working for me. Like many others, I need to pass 443 to a VM which expects its own SSL service for a domain, NPM takes SSL service for other domains.

With your solution, I add the script to stream.conf in Custom folder and update it with my own information accordingly. I can confirm the stream.conf has been read by nginx, but it doesn't pass 443. I mean if I request HTTPS to the domain, it responds "can't reach this page". HTTP works well to this domain which I pass HTTP with normal NPM forwarding configuration.

Did I miss any configuration requirement in addition to the "stream.conf" file? What is the "12346" port? As per your note - "Note that this approach makes designates an new port as the main input port for your NPM https traffic!", is this the port 12346 you referred to? Do I need to open this port or forward it from the firewall? or any recommended configuration? Thanks in advance.

bigdocloud avatar Jan 02 '25 22:01 bigdocloud

Issue is now considered stale. If you want to keep it open, please comment :+1:

github-actions[bot] avatar Nov 11 '25 02:11 github-actions[bot]