Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL Passthrough #853

Open
ctops1 opened this issue Jan 31, 2021 · 8 comments · May be fixed by #1479
Open

SSL Passthrough #853

ctops1 opened this issue Jan 31, 2021 · 8 comments · May be fixed by #1479

Comments

@ctops1
Copy link

ctops1 commented Jan 31, 2021

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

#235
#461

@ctops1 ctops1 changed the title SSL PassThru SSL Passthrough Jan 31, 2021
@ma-karai
Copy link

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.

@ctops1
Copy link
Author

ctops1 commented Feb 25, 2021

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.

@centralhardware
Copy link

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.

@chaptergy chaptergy linked a pull request Oct 12, 2021 that will close this issue
@chaptergy
Copy link
Collaborator

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!

@gabuzi
Copy link

gabuzi commented May 28, 2023

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

Copy link

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

@github-actions github-actions bot added the stale label Mar 20, 2024
@jdkruzr
Copy link

jdkruzr commented Mar 22, 2024

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.

@gabuzi
Copy link

gabuzi commented Mar 31, 2024

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 #1114

@github-actions github-actions bot removed the stale label May 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants