Miha Jamsek Miha Jamsek - 4 days ago 6
Apache Configuration Question

Serve HTTPS NodeJS website over virtual host

I have a webb app that runs on my domain.
First I had regular http site, which was running on port 8082 on my server, while apache server (through virtual host) was redirecting requests from port 80 to localhost:8082.

Now I got a SSL certificate that i tried to install with my site. I made https server in Node, and on https://localhost:8082 everything worked as it should.

But then I tried making my domain to be served over https. Now I tried editing my virtual host file to acknowledge certificate, but I think I haven't written it correctly. Also, I tried that it would just redirect it to https instead of http, but nothing like this works. Since there is a serious shortage of tutorial on this exact matter, I don't know how to proceed.

What should I do?
Run http or https server?
What should i write in virtual host file?

EDIT:
My virtual host file:

<VirtualHost *:80>
ServerName www.domain.com
Redirect permanent / https://www.domain.com
</VirtualHost>

<VirtualHost *:443>

ServerName domain.com
ServerAlias www.domain.com

SSLEngine On
SSLProxyEngine On
SSLCertificateFile "/home/USERNAME/Projects/NODEAPP/chained.pem"
SSLCertificateKeyFile "/home/USERNAME/Projects/NODEAPP/domain.key"

ProxyPreserveHost On
ProxyRequests off

<Proxy *>
Order deny,allow
Allow from all
</Proxy>

<Location />
ProxyPass http://localhost:8082/

ProxyPassReverse http://localhost:8082/
</Location>

</VirtualHost>

Answer

(Fair warning: my understanding of your question is that you want https://www.domain.com to appear in the browser's URL bar, not https://www.domain.com:8082. If that's not the case a lot of the below is wrong, but will probably still help you understand your problem better.)

Background

(aka, HTTP design crash course because I think you don't quite understand what's going on, but feel free to skip if you do:)

I think you have a misconception as to what Apache's doing - mostly because you're saying that it's "redirecting" requests.

An HTTP redirect is any(?) response with a status code in the 3xx range, and it tells your browser that the resource it's requested has moved somewhere else. For example, let's say that http://example.com/foo used to exist, but has since been moved to http://example.org/bar (note the .com and .org difference). If you type in http://example.com/foo into your URL bar, your browser will request /foo (specifically it will send an HTTP GET request) to example.com. example.com will then send an HTTP response back to the browser redirecting it to http://example.org/bar. Then the browser will send an HTTP GET request for /bar to example.org and get back a response containing the resource.

Here's a diagram:

Step 1:

-------------                                   --------------------------
|           |                                   |                        |
|  Browser  | -------- HTTP GET /foo ---------> | example.com web server |
|           | <-- HTTP 301 Moved Permanently -- |                        |
-------------                                   --------------------------

Step 2:

-------------                        --------------------------
|           |                        |                        |
|  Browser  | --- HTTP GET /bar ---> | example.org web server |
|           | <---- HTTP 200 OK ---- |                        |
-------------                        --------------------------

Now, here's the thing. That's not what you're doing, nor probably what you want to be doing. What you're doing is called reverse proxying, which is where whenever the web server (in your case Apache) receives a request, it just forwards it somewhere else. In your original (all-HTTP) setup, the web browser would request www.domain.com from Apache, then Apache would simply forward that request, with little to no modification, to http://localhost:8082. Here's the diagram:

Step 1:

-------------                        ----------                        ------------------
|           |                        |        |                        |                |
|           | ----- HTTP GET / ----> |        |                        |                |
|  Browser  |                        | Apache | ----- HTTP GET / ----> | localhost:8082 |
|           |                        |        | <---- HTTP 200 OK ---- |                |
|           | <---- HTTP 200 OK ---- |        |                        |                |
-------------                        ----------                        ------------------

Step 2:

There is no step 2. BOOM.

The really important thing to note here is that the browser doesn't "see" Node.js, it sees Apache on the other end. Likewise Node.js doesn't "see" the browser, it sees Apache (although typically Apache will augment the request with details about the browser connection - see for example the X-Forwarded-For header).

This is an intentional part of the design of HTTP - HTTP request and response semantics don't imply anything about the internal implementation details of how requests are processed. That's why HTTP is so powerful. It lets you create really complex setups - think what you're doing but on a much bigger scale and with even more components - and then abstract over that complication with a simple, uniform interface that's shown to the external world: your URL hierarchy combined with the HTTP request/response cycle.

How to solve your actual problem

Now that we've established what you're trying to do, here's what your problem is: you're conflating what protocol Node.js is speaking with what protocol Apache is speaking (and thus what the external world is seeing).

Your end goal is for people to connect to your website over HTTPS, right? In that case, you don't need Node.js to speak HTTPS, because the only thing that directly talks to Node.js is Apache. Apache is the thing that speaks to browsers, so Apache is what you need to use HTTPS. Ideally, you'd have Apache speak HTTPS to browsers and HTTP to Node.js - it doesn't matter if Apache encrypts traffic to Node because Node is on localhost. In diagram form:

-------------                         ----------                        ------------------
|           |                         |        |                        |                |
|           | ----- HTTPS GET / ----> |        |                        |                |
|  Browser  |                         | Apache | ----- HTTP GET / ----> | localhost:8082 |
|           |                         |        | <---- HTTP 200 OK ---- |                |
|           | <---- HTTPS 200 OK ---- |        |                        |                |
-------------                         ----------                        ------------------

So make your Node app listen for HTTP requests, then fix your Apache config to serve stuff with HTTPS, but speak HTTP to the Node backend.

I think what's wrong with your configuration is that you need quotes around the path, i.e. <Location "/">. Alternately in the reverse proxy configs that I have, I have ProxyPass directives set up without a <Location> block, like so:

ProxyPass / http://localhost:8082/
ProxyPassReverse / http://localhost:8082/

so you could try that too.

If it still doesn't work, try removing the <Proxy>/</Proxy> block - I'm not really sure what that does, but I don't have anything like that in my reverse proxy configurations (which work) - so it might be interfering with something.

As for your HTTP to HTTPS redirect, I don't really see anything wrong with it. Are you sure you're connecting to www.domain.com instead of domain.com? The second VirtualHost serves both, but the first serves only the former.

Comments