Deploy a Node.js App to DigitalOcean with SSL
This step-by-step tutorial walks through the process of deploying a Node.js app to a DigitalOcean droplet with free SSL from Let’s Encrypt for $5/month.
In this post, we’ll walk through the process, from start to finish, of creating a new server, deploying a Node.js app, securing it (for free!) with an SSL certificate, and pointing a domain name to it.
Watch the video above to see the whole process live — with clever commentary, of course — or jump to just the bits you need in the write-up below.
Prerequisites
- A domain name that you can modify DNS records for.
- A sense of adventure.
Set Up and Configure Your Server
Before we can do anything, we need a server that can be accessed publicly. There are lots of options out there, so don’t feel locked into DigitalOcean — however, for this tutorial it’ll probably be easiest to follow if you’re using exactly the same setup.
Create a new droplet on DigitalOcean.
To start create an account on DigitalOcean, or log into your existing account.
Once you’re logged in, click the “Create Droplet” button at the top of your screen.
Choose the $5/month option with Ubuntu 16.04.1 x64. Select a region closest to your users.
Finally, add your SSH key and
How to find your SSH key
First, open Terminal1 and check for existing SSH keys:
If you already have SSH keys set up, you should see a file called id_rsa.pub
. (If there’s a file ending in .pub
, it’s very likely an SSH key.)
To copy your SSH key, use one of the following commands:
Add your SSH key to the droplet
Back on the DigitalOcean droplet creation screen, click the “New SSH Key” button and paste your SSH key into the field that opens.
Click “Add SSH Key” to save it, then make sure it’s selected, name your droplet, and hit the big “Create” button to get your server online.
Your new droplet will display its IP address once it’s set up. You can click on it to copy the IP to your clipboard.
Connect to the server using SSH
DigitalOcean droplets are created with a root
user, and since we added our SSH keys, we can now log in without a password. Like magic!
You will most likely be asked if you want to continue connecting the first time you log in. Type yes
to continue, and you’ll see something similar to the following:
Configure the server with basic security.
Once we’re logged into the server, we need to get a few things configured to keep it secure.2
Create an SSH user
First, we’re going to add a new user with sudo
privileges. To do this, run the following command while logged into your droplet:
This command prompts us for a password, and then for some additional, optional details.
Afterward, we can see that our user has been created by running id <your_username>
, which should output something like the following:
In order to run some of the commands on the server, such as restarting services, we need to add our new user to the sudo
group. Do this by running the following command:
Now if we run id jason
we can see the sudo
group has been applied.
Add your SSH key for the new user
Next, we need to add our SSH key to the new user. This allows us to log in without a password, which is important because we’re planning to disable password logins for this server.
The nano
editor allows us to copy-paste your SSH key — the same one we copied to DigitalOcean when we created the droplet — into the new file, then press control + X
to exit. Type Y
to save the file, and press enter
to confirm the file name.
We can make sure the SSH key is saved by running cat ~/.ssh/authorized_keys
; if the SSH key is printed in the terminal, it’s been saved.
Disable password login
Since every server has a default root
account that’s a target for automated server attacks — and because that account has unlimited power inside the server — it’s a good idea to make sure no one can use it.
After the previous step, you should be logged into your server as root
. Let’s make sure the new account works and has sudo
access:
Inside, we need to update the SSH configuration to disable password logins, and to disable logging in as root
altogether.
To do this, use the following command to open the SSH configuration file for editing:
Inside, you need to update two settings:
- Find
PermitRootLogin yes
and change it toPermitRootLogin no
- Find
#PasswordAuthentication yes
and change it toPasswordAuthentication no
Save the file by pressing control + X
, then Y
, then enter
.
Finally, restart the SSH service with this command:
Test your login by opening a new tab in Terminal (command + T
on Mac) and logging into your server again.
If we log in as our new user, everything works as expected. However, if we try to log in as root
, we get an error:
Set up a basic firewall
Next, we’re going to configure a simple firewall. We’re going to configure it to deny all traffic except through standard web traffic ports (80
for HTTP, and 443
for HTTPS), and to allow SSH logins.
This, in theory at least, should eliminate a lot of security risks on our server. (But again — this is not a security article; these are just basic precautions.)
We’re going to run three commands to configure the firewall — called ufw
— and then we’ll enable it. Enter the following while logged into the server:
To check the status of the firewall, run sudo ufw status
, which will give you the following:
Get Your App Up and Running
Now that the server is set up, we can get our app installed.
Install Git.
In order to get a copy of our app to this server, we’re going to use Git. Fortunately, Ubuntu makes it really easy to install common tools, so all we need to do is run this command:
We can validate that Git was installed properly by running git --version
:
Set up Node.js.
Node.js is a little more complex than Git, because there are several different versions of Node that are used in production environments. Therefore, we need to update apt-get
with the right version for our app before we install it.
Tell apt-get
which Node.js version to download
The folks at NodeSource have made it really easy to install our desired Node version. For this tutorial, we’ll be using the latest 6.x
release.
Run the following commands to download and execute the setup script:
This takes a few seconds to complete.
Install Node.js v6.x
With the NodeSource script complete, we can simply use apt-get
to install Node.js:
Once it’s complete, we can verify that node
is available by running node --version
:
Clone the app
Now we can actually clone a copy of our app to the server — things are really getting exciting now.
It doesn’t matter where you install the app, so let’s create an apps
dir in our user’s home folder and clone the app into a folder named after our domain — this makes it really easy to remember which app is which.
Test the app
To make sure your app is installed and working, move into the new folder and start it:
The example app listens at http://localhost:5000
, so we can test if it’s working by opening a new Terminal session, logging into our server, and making a curl
request to the app.
Awesome — we have a running app. Now we just need to make it accessible to the outside world.
We can exit
from the test session (the one we just ran the curl
command in), and we can stop the app in our other session using control + C
.
Start Your App Using a Process Manager
Simply starting the app manually is technically enough to get the app deployed, but if the server restarts, that means we have to manually start the app again.
And in production apps, we want to eliminate as many — if not all — manual steps to get the app deployed. So we’re going to use a process manager called PM2 to run our app. This also gives us benefits like easy-to-access logs, and a simple way to start, stop, and restart the app.
PM2 also allows us to start the app automatically when the server restarts, which means one less thing we need to worry about.
Install PM2
Unlike the other tools we’ve installed, pm2
is a Node package. We install it using the npm
command, which is the default package manager for Node.js.
Start your app using PM2
With PM2 installed, we can now start the app like this:
Once the app is started, we see the status displayed:
And, conveniently, the app is running without locking up our session. In the same session we’re able to run curl http://localhost:5000
to make sure it’s running properly:
Start your app automatically when the server restarts
We’re almost done with getting the app running — just one more step.
The last thing to do is to make sure that when the server restarts, PM2 starts our app again.
This is a two-step process, which we kick off by running pm2 startup systemd
:
PM2 prints out a command that we need to run using sudo
. Copy-paste that to finish the process.
This will step through the process of updating the server to run PM2 on startup, and then you’re all set.
Now we’ve got a running app — in the next section, we’ll make the app securely accessible to the rest of the world!
Get a Free SSL Certificate With Let’s Encrypt
SSL was a big hurdle for a long time for two reasons:
- It was expensive.
- It was hard.
Fortunately, some very smart, very kind-hearted people created Let’s Encrypt, which is:
- Free
- Easy
So now there’s really no excuse not to set up SSL for our domains.
Install Let’s Encrypt
To start, we need to install some tools that Let’s Encrypt depends on, then clone the letsencrypt
repository to our server.
Configure your domain to point to the server
Log into your DNS provider. I use CloudFlare; you’ll want to log into whichever service you either bought your domain name through (Namecheap and GoDaddy, for example), or the service you use to manage your DNS (such as CloudFlare or DNSimple).
Add an A record for your domain that points to your droplet’s IP address.
To check that the domain is pointing to your droplet, run the following (make sure to replace app.example.com
with the domain you just configured):
Generate the SSL certificate.
Now that the domain is pointed to our server, we can generate the SSL certificate:
The tool will run for a while to initialize itself, and then we’ll be asked for an admin email address, to agree to the terms, and to specify our domain name or names. Once that’s done, the certificate will be stored on the server for use with our app.
For now, that’s all we need. We’ll come back to these in a minute when we configure the domain.
Setup auto-renewal for the SSL certificate
For security, Let’s Encrypt certificates expire every 90 days, which seems pretty short. (By contrast, most paid SSL certificates are valid for at least a year.)
It turns out, though, that Let’s Encrypt has an one-step command to renew certificates:
This command checks if the certificate is near its expiration date and, when necessary, it generates an updated certificate that’s good for another 90 days.
Now, it would be a huge pain in the ass if we had to manually log into the server and renew the certificate four times a year — and most likely we’d end up forgetting at least once — so we’re going to use a built-in tool called cron
to handle the renewal automatically.
To set this up, run the following command in the terminal to edit the server’s cron
jobs:
We get an option for which editor to use here. Since nano
is easier than the others, we’ll stick with that.
When the editor opens, head to the bottom of the file and add the following two lines:
The first line tells cron
to run the renewal command, with the output logged so we can check on it when necessary, every Monday at 1 in the morning.
The second restarts NGINX — which we haven’t set up yet, so don’t worry — at 1:30 to make sure the new cert is being used.
Save and exit by pressing control + X
, then Y
, then enter
.
That’s it for the SSL cert. The last thing left is to make your app accessible by visiting our domain name in a browser.
Point Your Domain to the App
In order to make our app accessible, we need to send visitors to our domain to our app. To do this, we’ll be using NGINX as a reverse proxy because it’s faster and less painful than handling it through Node.js.
Install NGINX
Installing NGINX is no different from most of the other tools we’ve downloaded so far. Use apt-get
to download and install it:
Make sure all traffic is secure
Next, we need to make sure that all traffic is served over SSL. To do this, we’ll add a redirect for any non-SSL traffic to the SSL version. That way, if someone visits http://app.example.com
, they’ll be automatically redirected to https://app.example.com
.
To do this, we need to edit NGINX’s configuration files. Run the following command to open the file for editing:
Inside, delete everything and add the following:
Save and exit by pressing control + X
, then Y
, then enter
.
Create a secure Diffie-Hellman Group
It only takes a couple extra minutes to create a really secure SSL setup, so we might as well do it. One of the ways to do that is to use a strong Diffie-Hellman group, which helps ensure that our secure app stays secure.
Run the following command on your server:
This takes a minute or two — encryption should be hard for computers — and when it’s done we can move on for now. We’ll use this file in the next section.
Create a configuration file for SSL
Since I’m not a security expert, we’re going to defer to an actual security expert for NGINX’s SSL settings.
We need to create a new file on our server to hold these settings — if we add another domain to this server, we can reuse them this way — which we’ll do with the following command:
Inside, we can copy-paste the following settings.
Save and exit by pressing control + X
, then Y
, then enter
.
Configure your domain to use SSL
This is the last configuration step, I promise.
Now that we’ve got a certificate, a strong Diffie-Hellman group, and a secure SSL configuration, all that’s left to do is actually set up the reverse proxy.
Open the site configuration again:
Inside, add the following below the block we added earlier:
Save and exit by pressing control + X
, then Y
, then enter
.
This configuration listens for connections to our domain on port 443
(the HTTPS port), uses the certificate we generated to secure the connection, and then proxies our app’s output out to the browser.
Before we start the server, we should test the NGINX configuration with sudo nginx -t
. If we didn’t make any typos and everything looks good, we’ll get the following:
Enable NGINX
The very last step in this process is to start NGINX.
Test Your App
And now: the big moment. We can now visit our domain in a browser, and we’ll see our app.
The configuration steps get pretty mind-numbing toward the end, but there’s a huge payoff: we can now bask in the glory of a server that took about 30 minutes to set up, costs $5/month, and — as a bonus — gets an A+ for SSL security.
Not bad for 30 minutes’ worth of setup, right?
Additional Resources
- Common firewall rules and commands
- How to configure NGINX as a reverse proxy for subdomains
- Setting up a Ubuntu 16.04 server with a new user and a basic firewall
- DigitalOcean’s guide to setting up a Node.js app for production
- Securing NGINX with Let’s Encrypt