Making Node.JS Work With Apache

While I’m on a roll with node-related posts, I would like to take a moment to discuss how to run Node with Apache. Now, this might seem counter-intuitive at first glance; I am sure you are asking yourself “why the heck would I need to run apache with node if a node web app itself runs its own HTTP server?” but that’s the wrong question to be asking. The right question is “What kind of advantages do I gain by using the two together?” and the answer may surprise you.

Why Node + Apache?

There are several huge advantages to be gained.

  • You can set up a ‘under maintenance’ page which will be displayed when your app crashes or is restarting (instead of the user getting a connection refused message)
  • You can run not just your one node web app on the HTTP port but multiple web apps, or even non-node websites
  • You can seamlessly combine functionality from other languages like PHP with your node app (say if you’re running node as a REST API and using PHP to actually display content)
  • You can scale your application by performing load balancing for production environments

These are just some of the ‘pros’ to using apache with node. I’m sure there are many others, and I’ll try to add them later if I think of any more.

How does it work?

It may sound a little preposterous to someone who has never done this before, and certainly that was me when I first read about this, but it’s actually quite an ingenuous solution on how to make these two technologies work together. The main part of this lies in Apache’s “Reverse Proxy” functionality. Confused? Well, don’t be. A reverse proxy is exactly what it sounds like – a proxy that works in reverse. I’m going to assume you know how a proxy works – that is, say you’re on a closed network and need to request a document from the internet; you connect to a proxy server instead of directly to the server that has the document and then ask it to retrieve it for you. The proxy server goes out to the actual server that holds the document you want, retrieves it, and then passes it along to you. A reverse proxy is the mirror image of that – you request a document from the server that you believe directly holds it, but that server in fact goes out and fetches it from [usually] some internal, pre-configured other server and then passes it along to you. Here’s a step-by-step summary of how it works:

  1. User requests mydomain.com/myfile.html
  2. The apache server at mydomain.com recognizes the mydomain.com vhost but does not have the myfile.html document – it is on a node server
  3. The apache server would have been preconfigured to take all requests for myfile.html (or some regex, or all requests) and forward them to some other server (in our case, a node web app) let’s say localhost:8000 on the same apache server for our example
  4. The node web app at localhost:8000 receives the request from the apache server which is acting like a proxy, processes the request, and sends it back the myfile.html content
  5. The apache server now forwards myfile.html contents to the user who originally requested it
  6. The user has now received myfile.html; however as far as they are aware the file came from mydomain.com not from localhost:8000 on the same server

Simple!

In the midst of all this, we can have additional directives to apache that can describe what kind of page to return in case the node server is down.

Getting Started with Configuring Apache as a Reverse Proxy

So before anything else, you will have to create the proper vhost for your site. In our example, let’s suppose we’re trying to set up the pastebin nodejs app I made as pastebin.mydomain.com where mydomain.com is configured to point to some server running apache on port 80. Our pastebin application is listening on port 8000 and we have it running via forever in a continuous loop. Open up your apache2 configuration (for me it is located in /etc/apache2/sites-available with a separate file per vhost/site) and create the vhost as follows:

<VirtualHost *:80>
ServerName pastebin.mydomain.com
ServerAlias www.pastebin.mydomain.com
DocumentRoot /var/www/pastebinjs/
Options -Indexes
ErrorDocument 503 /maintenance.html
</VirtualHost>

So what does this get us? Let’s go through it line by line:

  1. Apache is going to listen on port 80 at any address/host for the connections to this vhost
  2. The specific host header we’re looking for is ‘pastebin.mydomain.com’
  3. We allow an alias called ‘www.pastebin.mydomain.com’ for some legacy support and fool-proofing
  4. We point all requests to the document root of /var/www/pastebinjs/
  5. Disable indexes as a bit of a security thing and just good practice
  6. Set up a custom 503 (“Service Unavailable”) error page to be /maintenance.html on the server

The final result is that we have a basic non-node website rooted at /var/www/pastebinjs with a custom error message. In this folder we would create maintenance.html and put some message like “we are under maintenance and looking into it! We’ll be back shortly!” so as not to scare the users. But now what? What about the node app? Alright, so the next part is almost as simple. We expand our vhost to the following:

<VirtualHost *:80>
ServerName pastebin.mydomain.com
ServerAlias www.pastebin.mydomain.com
DocumentRoot /var/www/pastebinjs/
Options -Indexes
ErrorDocument 503 /maintenance.html

ProxyRequests on
ProxyPass /maintenance.html !
ProxyPass / http://localhost:8000/
</VirtualHost>

What do we have now then? Well, we enabled the proxy module in apache and we told it that maintenance.html will not be proxied through but will instead be served by the apache server as a ‘normal’ page (the ! means it won’t be sent through; for more info on the proxypass directive see the apache official documentation). This directive is necessary because in the next line we pass all requests through to localhost:8000 running our node app which means if it crashed, the 503 “service unavailable” custom error page would not be reachable as it will be passing the request for /maintenance.html to the node server which is down.

That’s basically it!

Security Heads Up

Couple things security related here, in case they are not obvious. Firstly, if you’re intending users to only use your app via the apache reverse proxy, ensure that your firewall is blocking outside requests from going directly to the node app! Make it only accessible to your apache server and nothing else as otherwise your users will be able to potentially mess with your application by accessing it directly (which may be a case you forgot to account for etc.)

Moreover, for your node application’s logging purposes, please understand that you will not be able to make use of any kind of ‘remote ip’ detection by literally looking at the remote IP. Your node app will receive all its requests from the reverse proxy, therefore the remote IP will always be the reverse proxy’s IP (e.g. 127.0.0.1 in our example) and not the actual client. To get around this, proxy servers by default append a header to their request called “X-Forwarded-For” which is a comma separated list of IP’s for which the request was proxied. According to wikipedia it takes the form:

X-Forwarded-For: client, proxy1, proxy2

However, in practice it may be the reverse order (at least for me it was, so do some experimenting to see how your webserver handles it). Here’s a small node.js function I wrote to get the remote IP of a client which automatically detects if it’s via proxy or direct access (note that ‘req’ refers to the req object passed by express.js):

// gets the remote IP from Express. Falls back to x-forwarded-for when available.
exports.getRemoteIp = function(req) {
 // get the remote IP
 var remotehost = req.connection.remoteAddress;
 if(typeof(req.headers["x-forwarded-for"]) !== 'undefined')
 {
 var forwardedFor = req.headers["x-forwarded-for"].split(",");
 remotehost = forwardedFor[forwardedFor.length-1].trim();
 }
 return remotehost;
}

What about only partially redirecting to node?

For some applications you may want to serve your frontend stuff via apache directly, and possibly even use PHP to generate some content and then just have node acting as an API. To do that, simply change your ProxyPass directive from the vhost configuration to read something like:

ProxyPass /api http://localhost:8000/

Now all requests sent to /api will be instead re-routed to the node app, but all other requests will go through to the usual document root. Note that apache will automatically reformat the URL; that is if the user requests /api/a/b/c then the node app will only receive the request for /a/b/c without the /api part as that is not mentioned in the ProxyPass directive for the 2nd parameter. Very useful!

Conclusion

Node and Apache work together quite well and you can accomplish a lot by harnessing their joint strengths. I’d recommend using this configuration or at least something similar for anything more than a local or development server. I hope this guide has helped you get set up for your next node project.

11 Comments

Jon Haugsand says:

We’re using it this way. We also make Apache httpd terminate the ssl connection and verify client certificates. However, here is where we have a challenge: We want the node express server to read the client certificates forwarded by Apache. This isn’t solved yet.

Anyone?

asdfsadf says:

something like:

RequestHeader set SSL_CLIENT_S_DN “%{SSL_CLIENT_S_DN}s”
RequestHeader set SSL_CLIENT_I_DN “%{SSL_CLIENT_I_DN}s”

comes to mind. Didn’t try actually, just planning the same for myself/

Damith says:

I have a small question. I’m running an apache server with number of virtual hosts and all based on PHP but I have a nodejs run on separate server with static IP.

I need to use nodejs(run on remote server) with my PHP applications runs on apache. How can I do that.

* Petro says:

You can still do the same reverse proxy to a remote server, but then you’re adding an extra step between your user and the node.js service. If your node.js application is hosted in the same data center as the Apache server then this shouldn’t be a big deal, but another thing you can consider is just putting Apache (or something lighter weight like NGINX) on the server that’s running your node app. If the node server has a different domain than your Apache server and if you’re using it as a REST API or something of that nature, you may need to implement the ability to use JSONP on both sides.

Arend says:

Thank you for this handy tutorial!

C.S. Taylor, Jr. says:

I got node.js going, but the 503 error document doesn’t work. I had to set up 500, and then it only works on url.com, not url.com:8000, and it always tells me it’s down, and on url.com:8000 I get connection refused if the node.js app isn’t running. Any advice here?

* Petro says:

Hey, the error page will only work when accessing the site from the apache port; I am assuming that 8000 is your node.js app so it won’t work there because that’s never going to hit apache. If it says it’s down, that means you misconfigured the proxy (e.g. it is not pointing to the correct host/port) so double check those settings. As for the connection refused part, could be that your node.js app is either not running or not listening on that host/port.

Jack Lee says:

Hey,I got a problem ,how can i set apache
proxy to make api/ and api/a or api/a/b all
available?

I am trying to use proxy pass with https but its not working please advise.

ProxyPass https://127.0.0.1:3500
ProxyPassReverse https://1127.0.0.1:3500

Error Not found How ever the same process working fine with http

ProxyPass http://127.0.0.1:3500
ProxyPassReverse http://1127.0.0.1:3500

http://mydomain.com/myapi

David Wakelin says:

If your node.js app is on the same server, then you don’t need SSL.
Just block that port (3500 in your case) at the firewall.

Evans says:

Thanks for this! Awesome post. For all my years on the internet I’ve never really understood the purpose of the reverse proxy.

Leave a Reply

Your email address will not be published.