Virtual Private Servers (VPS), Dedicated Servers, or Infrastructure as a Service (IaaS for example, the likes of Amazon EC2 or Rackspace) and owning our own server machines all have one thing in common: total control over the server environment.
However, with great power comes great responsibility, and there are a few challenges we need to be aware of. This recipe will demonstrate how to overcome these challenges as we safely initialize a Node web app on port 80
.
We will, of course, need a remote server environment (or our own setup). It's important to research the best package for our needs.
Dedicated Servers can be expensive. The hardware to software ratio is one to one, we're literally renting a machine.
VPS can be cheaper since they share the resources of a single machine (or cluster), so we're only renting out the resources it takes to host an instance of an operating system. However, if we begin to use resources beyond those assigned, we could hit penalties (downtime, excessive charges) since over usage can affect other VPS users.
IaaS can be relatively cheap, particularly when up-scaling is involved (when we need more resources), though IaaS tends to contain a pay-as-you-go element to its pricing which means the costs aren't fixed and could require extra monitoring.
Our recipe assumes the usage of a Unix/Linux server with the sshd
(SSH Service) running. Furthermore, we should have a domain pointed at our server. In this recipe, we'll assume the domain name as nodecookbook.com
. Finally, we must have Node installed on our remote server. If difficulties arise, we can use the instructions available at https://www.github.com/joyent/node/wiki/Installation, or for installing via a package manager we can use the instructions at https://www.github.com/joyent/node/wiki/Installing-Node.js-via-package-manager.
We'll be deploying the login
app from the second-to-last recipe ofChapter 6, Accelerating Development with Express, so we need this at hand.
To ready our app for transfer to the remote server, we'll remove the node_modules
folder (we can rebuild it on the server):
rm -fr login/node_modules
Then we compress the login
directory by executing the following:
npm pack login
This will generate a compressed archive named after the app's name and version as given in the package.json
file, which will generate the filename application-name-0.0.1.tgz
for an untouched Express generated package.json
file.
Whatever npm pack
called it, let's rename it to login.tgz:
mv application-name-0.0.1.tgz login.tgz #Linux/Mac OS X
rename application-name-0.0.1.tgz login.tgz ::Windows.
Next, we upload login.tgz
to our server. For example, we could use SFTP:
sftp [email protected]
Once logged in to the via SFTP, we can issue the following commands:
cd /var/www
put login.tgz
It's not necessary to upload to the /var/www
directory, it's just a natural place to put a website.
This assumes that we have SFTPed into our server from the directory holding login.tgz
.
Next, we SSH into the server:
ssh -l root nodecookbook.com
If we're using a Windows desktop, we could SFTP and SSH into our server using putty: http://www.chiark.greenend.org.uk/~sgtatham/putty/.
Once logged in to the remote server, we navigate to /var/www
and decompress login.tar.gz:
tar -xvf login.tar.gz
As login.tar.gz
decompresses, it recreates our login
folder on the server.
To rebuild the node_modules
folder, we enter the login
folder and use npm
to regenerate the dependencies.
cd login
npm -d install
Most servers have a shell-based editor, such as nano, vim
, or emacs
. We can use one of these editors to change one line in app.js
(or otherwise SFTP over a modified app.js):
app.listen(80, function () { process.setuid('www-data'), });
We're now listening on the standard HTTP port, meaning we can access our app without suffixing a port number to its web address. However, since we'll be starting the app as root
(necessary in order to bind to port 80)
, we also pass a callback to the listen
method which changes access privileges of the app from root
to www-data
.
In some cases, dependent upon file permissions, reading or writing to files from our app may no longer work. We can fix this by changing ownership:
chown -R www-data login
Finally, we can start our app with:
cd login
nohup node app.js &
We can ensure that our app is running as www-data
with:
ps -ef | grep node
We modified app.listen
to bind to port 80
and added a callback function that resets the user ID from root
to www-data
.
Adding a callback to listen
isn't limited to Express, it works the same way with a plain httpServer
instance.
Running a web server as root
is bad practice. If our app was compromised by an attacker, they would have root
access to our system via our app's privileged status.
To demote our app, we call process.setuid
and pass in www-data. process.setuid
. This takes either the name of a user, or the user's UID. By passing in a name, we cause process.setuid
to block the event loop (essentially freezing operations) while it cross-references the user string to its UID. This eliminates the potential sliver of time where the app is bound to port 80
and also running as root
. In essence, passing a string to process.setuid
instead of the underlying UID means nothing can happen until the app is no longer root
.
We call our process with nohup
and follow up with the ampersand (&). This means we freely end our SSH session without causing our app to terminate along with the session.
The ampersand turns our process into a background task, so we can do other things (like exit)
while it runs. nohup
means ignore the hangup signal (HUP). HUP is sent to any running processes initiated via SSH whenever the SSH session is terminated. Essentially, using nohup
allows our web app to outlive the SSH session.
There are other ways to start our app independent from our session, and to bind to port 80
without running the app as root
. Plus, we can also run multiple apps and proxy them to port 80
with http-proxy
.
An alternative to using nohup
to achieve independence from our SSH session is screen
. We would use it as follows:
screen -S myAppName
This would give us a virtual terminal, from which we could say:
cd login
node app.js
Then we could leave the virtual terminal by pressing Ctrl + A
followed by D
. We would return to our initial terminal. The virtual terminal would continue to run after we had logged out of SSH. We could also log back in to SSH at any time and say:
screen -r myAppName
Where we would be able to see any console output and stop (Ctrl + C)
and start the app.
For this example, we should SSH into our server as a non-root user:
ssh -l dave nodecookbook.com
An alternative way to bind to port 80
is with authbind
, which can be installed via our server's package manager. For instance, if our package manager is apt-get
we could say:
sudo apt-get install authbind
authbind
works by preempting the operating system policies on port binding and exploiting an environment variable called LD_PRELOAD
upon execution. Therefore, it never needs to be run with root
privileges.
To get it working for us we have to perform some simple configuration work as follows:
sudo touch /etc/authbind/byport 80
sudo chown dave /etc/authbind/byport 80
sudo chmod 500 /etc/authbind/byport 80
This tells authbind
to allow the user dave
to bind processes to port 80
.
We no longer need to change the process UID, so we edit the penultimate line of app.js
to:
app.listen(80);
We should also change ownership of the login
folder as follows:
chown -R dave login
Now we can start our server without touching the root
access at all:
nohup authbind node app.js &
authbind
can cause our app to work out of the box, no modifications necessary. However, it currently lacks IPv6 support so it's not yet future-proof.
What about serving multiple processes with the default HTTP port?
We can achieve this with the third-party http-proxy
module.
npm install http-proxy
Let's say we have two apps one (our login
app) to be hosted at login.nodecookbook.com
and the other (the server.js
file from the very first recipe of this book) to be simply at nodecookbook.com
. Both domains point to the same IP.
server.js
will be listening on port 8080
, and we'll modify login/app.js
to listen again to port 3000
as shown in the following code:
app.listen(3000, '127.0.0.1'),
We also added a second argument defining what address to bind to (rather than any address). This prevents our server from being accessed by port.
Let's create a file in a new folder, call it proxy.js
, and write the following:
require('http-proxy') .createServer({router : { 'login.nodecookbook.com': 'localhost:3000', 'nodecookbook.com': 'localhost:8080' }}).listen(80, function () { process.setuid('www-data'), });
The object passed to createServer
contains a router property, which in turn is an object instructing http-proxy
to route incoming traffic on a particular domain to the correct locally-hosted process according to its port.
We finish off by binding to port 80
and degrading from root
to www-data
.
To initialize, we must do:
nohup node login/app.js &
nohup node server.js &
nohup node proxy.js &
Since we're binding our proxy server to port 80
, these commands must be run as root
. If we're operating SSH with a non-root account, we simply prefix these three commands with sudo
.
18.190.219.65