Handling http and https requests using a single port with nginx

30,753

Solution 1

According to wikipedia article on status codes, Nginx has a custom error code when http traffic is sent to https port(error code 497)

And according to nginx docs on error_page, you can define a URI that will be shown for a specific error.
Thus we can create a uri that clients will be sent to when error code 497 is raised.

nginx.conf

#lets assume your IP address is 89.89.89.89 and also that you want nginx to listen on port 7000 and your app is running on port 3000

server {
    listen 7000 ssl;
 
    ssl_certificate /path/to/ssl_certificate.cer;
    ssl_certificate_key /path/to/ssl_certificate_key.key;
    ssl_client_certificate /path/to/ssl_client_certificate.cer;

    error_page 497 301 =307 https://89.89.89.89:7000$request_uri;

    location / {
        proxy_pass http://89.89.89.89:3000/;

        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
}

However if a client makes a request via any other method except a GET, that request will be turned into a GET. Thus to preserve the request method that the client came in via; we use error processing redirects as shown in nginx docs on error_page

And thats why we use the 301 =307 redirect.

Using the nginx.conf file shown here, we are able to have http and https listen in on the same port

Solution 2

For those who might be searching:

Add ssl on; and error_page 497 $request_uri; to your server definition.

Solution 3

This is finally possible to do properly since 1.15.2. See the information here.

In your nginx.conf add a block like this (outside the http block):

stream {
    upstream http {
        server localhost:8000;
    }

    upstream https {
        server localhost:8001;
    }

    map $ssl_preread_protocol $upstream {
        default https;
        "" http;
    }

    server {
        listen 8080;
        listen [::]:8080;
        proxy_pass $upstream;
        ssl_preread on;
    }
}

Then you can create your normal server block, but listening on these different ports:

server {
    listen 8000;
    listen [::]:8000;
    listen 8001 ssl;
    listen [::]:8001 ssl;
...

This way, the stream block is able to preread and detect if it is TLS or not (on port 8080 in this example), and then proxy passes it to the correct server port locally.

Solution 4

If you wanted to be really clever, you could use a connection proxy thing to sniff the first couple of bytes of the incoming data stream, and hand off the connection based on the contents of byte 0: if it's 0x16 (the SSL/TLS 'handshake' byte), pass the connection to the SSL side, if it's an alphabetical character, do normal HTTP. My comment about port numbering applies.

Solution 5

Yes, it's possible, but needs patching the nginx source code (HoverHell has solution without patching). Nginx treats this as misconfiguration rather then valid configuration.

Variable $ssl_session_id can be used to differ between plain and ssl connection.

Patch against nginx-0.7.65:

--- src/http/ngx_http_request.c-orig    2011-05-03 15:47:09.000000000 +0200
+++ src/http/ngx_http_request.c 2011-05-03 15:44:01.000000000 +0200
@@ -1545,12 +1545,14 @@

    c = r->connection;

+    /* disable plain http over https port warning
     if (r->plain_http) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "client sent plain HTTP request to HTTPS port");
         ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
         return;
     }
+    */

#if (NGX_HTTP_SSL)

Server config:

server {
    listen 80;
    index index.html;

    location / {
        root html;
        if ($ssl_session_id) {
            root html_ssl;
        }
    }

    ssl on;
    ssl_certificate cert.crt;
    ssl_certificate_key cert.key;
}
Share:
30,753

Related videos on Youtube

alemartini
Author by

alemartini

Updated on September 17, 2022

Comments

  • alemartini
    alemartini over 1 year

    i was wondering if nginx is able to handle http and https requests on the same port. [*]

    This is what i'm trying to do. I'm running a web server (lighttpd) handling http requests, and a C program that serves a particular section of the document tree through https. These two processes run on the same server.

    At the firewall level, i can have only one port forwarding traffic into this server. So what i'd like to do is to set up nginx on this server so that it listens for requests on a single port and then:

    A) redirects all http://myhost.com/* requests so that they go to localhost:8080 (where lighttpd is listening)

    B) if a user requests a URL starting with, for example, https:// myhost.com/app, it sends that request to localhost:8008 (C program). Note that in this case, traffic between the remote browser and nginx must be encrypted.

    Do you think this could be possible? If it is, how can it be done?

    I know how to do this using two different ports. The challenge that i face is doing this with just a single port (unfortunately, i don't have control over the firewall configuration on this particular environment, so that's a restriction that i cannot avoid). Using techniques like reverse port fowarding through ssh to bypass the firewall won't work either, because this should work for remote users having nothing more than a web browser and an internet link.

    If this is beyond nginx capabilities, do you know of any other product that could meet this requirements? (so far i've been unsuccessful in setting this up with lighttpd and pound). I'd also prefer avoiding Apache (although i'm willing to use it if it's the only possible choice).

    Thanks in advance, Alex

    [*] Just to be clear, i'm talking about handling encrypted and unencrypted HTTP connections through the same port. It doesn't matter if the encryption is done through SSL or TLS.

  • alemartini
    alemartini almost 15 years
    Hi Wil, and thanks for your answer! I think that serving everything over https could be an option, although i'd like to be able to set this up in the way that i've described. Maybe this could be done if the front web server (acting as a reverse proxy) could establish a normal http session and then upgrade it to https without changing ports. I think that this behavior is described in RFC2817 (upgrading to TLS withing HTTP/1.1) but i'm not sure if nginx or other web servers know how to deal with that standard.
  • William Hilsum
    William Hilsum almost 15 years
    I don't have time to read a whole RFC (and not sure i'm smart enough to understand it!) but are you talking about the standard negotiation before the secure session is established or full blown different sessions? I think I understand a bit more - the server is serving on two ports and it is the proxy that refers the request - sounds cool but I have never seen it done. Perhaps a solution could be via creating a single secure site on one port and having a whole virtual directory that simply inherits / imports the other website? It doesn't get over all the issues, but may just work :S
  • alemartini
    alemartini almost 15 years
    Encrypted and unencrypted HTTP traffic can be handled through a single port. What i'd like to know is if this is possible by using nginx or other products (like lighttpd) acting as reverse proxies. Quite probably this kind of setup can be handled by Apache, but i forgot to mention in my original question that i'd rather prefer not having to use Apache (although i'd do it if there isn't any other choice on the Linux platform to accomplish this).
  • womble
    womble almost 15 years
    As I said in my answer, "I believe newer nginx release support [TLS upgrade], although I haven't tried". If you need me to read the manual for you, then you're out of luck.
  • alemartini
    alemartini almost 15 years
    I'm sorry if i gave the impression that i need someone to read a manual for me. It just seems that the problem (and the questions about it) weren't described precisely enough (my mistake), leading to different interpretations of what i was asking or needing. So i decided to open a new question about this issue and try to avoid any possible confusion with regard to the problem or the specific questions about it. Anyway, thanks for your time and for sharing your insight.
  • Mawi12345
    Mawi12345 about 12 years
    Little improvement to HoverHells answer: Add ssl on; and error_page 497 =200 $request_uri; to your server definition. This will change the status code to 200.
  • SiliconMind
    SiliconMind about 9 years
    This serve fault answer explains why this solution works.
  • mrtexaz
    mrtexaz over 3 years
    works great; on ubuntu 18.04, had to install nginx mainline package following instructions here: nginx.org/en/linux_packages.html#Ubuntu