24

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.

2
  • HTTPS requests go to port 443 by default, so even if you can get this working (and I think it's possible with a bit of hackery), you'd need to use yourhost.com and yourhost.com:80 as the links (or yourhost.com:443 and yourhost.com).
    – Zanchey
    Jul 30, 2009 at 5:26
  • Ok, i'm new at Server Fault and i don't know if i can delete my own question. Since it seems that the problem hasn't been formulated clearly enough, i'll open a new question instead. Anyway, thanks a lot to everyone who has contributed with useful suggestions for this issue.
    – alemartini
    Jul 31, 2009 at 1:04

9 Answers 9

22

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

19

For those who might be searching:

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

2
  • 9
    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.
    – Mawi12345
    Mar 19, 2012 at 15:25
  • This serve fault answer explains why this solution works. Apr 28, 2015 at 12:39
7

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.

1
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.

2

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;
}
1

I do not think there is anything that can handle two different protocols on a single port...

I am curious as to why you can only forward one port, but that aside... it is not ideal but if I was in your shoes, I would serve everything over https.

2
  • 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.
    – alemartini
    Jul 30, 2009 at 3:54
  • 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 Jul 30, 2009 at 4:14
1

You cannot support both HTTP and HTTPS over the same port, because both ends of the connection are expecting to talk a certain language, and they're not clever enough to work out if the other end is speaking something else.

As your comment to Wil's answer suggested, you could use TLS upgrade (I believe newer nginx releases support it, although I haven't tried), but that's not running HTTP and HTTPS, that's just running HTTP with TLS upgrade. The problem is still browser support -- most browsers (still) don't support it. If you've got a limited pool of clients, then this is a possibility, however.

3
  • 1
    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).
    – alemartini
    Jul 30, 2009 at 5:13
  • 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.
    – womble
    Jul 31, 2009 at 1:38
  • 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.
    – alemartini
    Aug 1, 2009 at 21:30
0

I'm not sure how it pulls it off, but CUPSD responds to both http and https on port 631. If nginx can't do that now, perhaps they can learn from how the CUPS team pulls it off, but CUPS is under the GPL, so nginx may have to look at changing its license if they do want to implement such a capability and can't find the code to do so elsewhere.

0

In theory, you could have a web page that is accessible via HTTP, which is able to open a WebSocket on https:443 to anywhere it wants to. The WebSocket initial handshake is HTTP. So, yes, it is possible to make a insecure looking page actually capable of secure communication. You could do this with the Netty Library.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .