Tutorial

How To Deploy Node.js Applications Using Systemd and Nginx

Published on November 27, 2013
author

Felix Saparelli

How To Deploy Node.js Applications Using Systemd and Nginx

Introduction

When deploying a web application to a Droplet, it might be tempting to simply use the same kind of setup as is used in development, i.e. starting the server by running “ruby app.rb” or “node server.js” in a terminal. This is simple and easy, while providing visible logs. One might even use “screen” or “tmux” or “nohup” to have it keep running even when the SSH session is dropped. This is dangerous: what happens if the server crashes and no one is around to restart it?

One could use forever and crontab to take care of this. This tutorial presents a more robust, albeit more complex, solution. Using systemd (available on Arch and Fedora, and CentOS in the future), web applications can be thoroughly managed: logs, uptime, resources and security through cgroups, and advanced daemon startup can all be accessed, controlled and fine-tuned in a unified manner.

This tutorial uses a simple Node.js application, but is applicable to most, if not all, others as well (be they Ruby, Python, etc). For PHP web applications, it is recommended to use a more specialized LAMP or LEMP stack instead.

Commands will be provided for both Fedora and Arch, do keep a lookout for which is which to avoid misconfiguration and/or confusion. When not indicated, the command is the same for both systems. It is also recommended you read through the entire tutorial before attempting it step-by-step, so as to get an idea of what it entails and whether it is appropriate for your situation.

System Preliminaries

  • A server with systemd. Arch Linux and Fedora droplets are configured thus by default.

  • Nginx, to be used as a reverse-proxy http and websocket server.

  • Git, to install nvm, and to pull your application if using git.

  • Root access. It is also possible to login as a normal user and sudo all commands, or to su - or sudo su - to a root prompt.

Install packages

Arch:

# pacman -Sy
# pacman -S nginx git

Fedora:

# yum install nginx git

Application Preliminaries

These are settings you can customise to your liking, but they have to be decided upon and set before starting.

User

The application will run in its own separate user account. Pick a name, it should relate to the application to make it easy to remember and maintain. Here, srv-node-sample is used.

# useradd -mrU srv-node-sample

Port

To avoid conflicts, pick a high port. Here, “15301” is used.

Application Setup

Start by installing what is necessary for the application to run. For Node.js (and Ruby, Python…), there are two choices: either use the system’s runtime, or a user-specific installation (e.g. using nvm, rbenv, RVM, virtualenv, etc).

Using the system node

Arch:

# pacman -S nodejs

Fedora:

# yum install nodejs

Using a user-specific install

This has to be installed in the application’s home directory, i.e. /home/srv-node-sample, which is most easily done by logging in as that user:

# su srv-node-sample
$ cd
$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh
$ source ~/.nvm/nvm.sh
$ nvm install 0.10
$ nvm alias default 0.10

Then take note of where the node binary is installed:

$ which node
/home/srv-node-sample/.nvm/v0.10.22/bin/node

Deploy your application

While logged in to srv-node-sample, deploy your code. This is an example only, your process will vary.

$ git clone git@server.company.tld:user/repo.git .
$ npm install
$ grunt deploy

For this tutorial, the following sample application is used:

js
var http = require('http');
http.createServer(function(req, res) {
    res.end('<h1>Hello, world.</h1>');
}).listen(15301);

Then return to root:

$ exit

Nginx Setup

This tutorial only briefly covers the configuration necessary, for a more thorough tutorial on configuring Nginx, see “How to Configure the Nginx Web Server” or the nginx manual.

Place this in your server block:

location / {
    proxy_pass http://localhost:15301/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Then set up its daemon:

# systemctl enable nginx
# systemctl restart nginx

Systemd Setup

Create a service file for the application, in /etc/systemd/system/node-sample.service.

There’s a few variables that need to be filled in:

  • [node binary] This is the output of “which node” as the srv-node-sample user. Either /usr/bin/node or the ~/.nvm/... path noted above.

  • [main file] This is the main file of your application. Here, ‘index.js`.

  • Don’t forget to replace srv-node-sample!

[Service]
ExecStart=[node binary] /home/srv-node-sample/[main file]
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=node-sample
User=srv-node-sample
Group=srv-node-sample
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Now start the service:

# systemctl enable node-sample
# systemctl start node-sample

Usage

Status

# systemctl status node-sample
node-sample.service
   Loaded: loaded (/etc/systemd/system/node-sample.service; enabled)
   Active: active (running) since Fri 2013-11-22 01:12:15 UTC; 35s ago
 Main PID: 7213 (node)
   CGroup: name=systemd:/system/node-sample.service
           └─7213 /home/srv-node-sample/.nvm/v0.10.22/bin/node /home/srv-nod...

Nov 22 01:12:15 d02 systemd[1]: Started node-sample.service.

Logs

# journalctl -u node-sample
-- Logs begin at Thu 2013-11-21 19:05:17 UTC, end at Fri 2013-11-22 01:12:15 UTC. --
Nov 22 01:12:15 d02 systemd[1]: Starting node-sample.service...
Nov 22 01:12:15 d02 systemd[1]: Started node-sample.service.
Nov 22 01:12:30 d02 node-sample[7213]: Sample message from application

Restart, stop, etc

Force a restart:

# systemctl restart node-sample

Stop the application:

# systemctl stop node-sample

The application will be automatically restarted if it dies or is killed:

# systemctl status node-sample
node-sample.service
   Loaded: loaded (/etc/systemd/system/node-sample.service; enabled)
   Active: active (running) since Fri 2013-11-22 01:12:15 UTC; 35s ago
 Main PID: 7213 (node)
   CGroup: name=systemd:/system/node-sample.service
           └─7213 /home/srv-node-sample/.nvm/v0.10.22/bin/node /home/srv-nod...

Nov 22 01:12:15 d02 systemd[1]: Started node-sample.service.

# kill 7213

# systemctl status node-sample
node-sample.service
   Loaded: loaded (/etc/systemd/system/node-sample.service; enabled)
   Active: active (running) since Fri 2013-11-22 01:54:37 UTC; 6s ago
 Main PID: 7236 (node)
   CGroup: name=systemd:/system/node-sample.service
           └─7236 /home/srv-node-sample/.nvm/v0.10.22/bin/node /home/srv-nod...

Nov 22 01:54:37 d02 systemd[1]: node-sample.service holdoff time over, sch...t.
Nov 22 01:54:37 d02 systemd[1]: Stopping node-sample.service...
Nov 22 01:54:37 d02 systemd[1]: Starting node-sample.service...
Nov 22 01:54:37 d02 systemd[1]: Started node-sample.service.

The PID has changed, showing the application has indeed been killed and restarted.

Websockets

If the application uses websockets, the following lines have to be added to the Nginx configuration:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;

and Nginx has to be reloaded:

# systemctl reload nginx

<div class=“author”>Submitted by: <a href=“https://passcod.name”>Félix Saparelli</div>

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Felix Saparelli

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
10 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Hi Félix, thanks for this super-helpful tutorial. I was using these steps to deploy a SailsJS application on a Fedora box and just ran into a single issue, which I want to share back in case somebody else might find it helpful:

When you setup the .service file for systemd, some Node apps might need to be started in their respective working directory (true for SailsJS). You can do this by adding a “WorkingDirectory” to the service file:

[Service] WorkingDirectory=/your/path

What if there is an error in application? Systemd will keep restarting service consuming 100% CPU?

Does anyone succeed at redirecting nginx logs to stderr (I want to see access logs via journalctl)? I’ve lost a few hours without any luck. No working example out there.

access_log /dev/stderr;
error_log /dev/stderr;

leads to open() “/dev/stderr” failed (6: No such device or address).

I’ve tried to change users, daemon modes, nginx command line options. No effect :(

If I have nginx proxy a different port (for eg. 8001) to 15301, it doesn’t work. Could be because port 8001 is not considered open?

I tried to follow this tutorial but when I go to the public IP address of my droplet, nothing is available. I wonder where it might go wrong?

systemctl status nginx says nginx is active and running with the following log

Nov 07 13:51:50 inspiredev nginx[30558]: nginx: the configuration file /etc/nginx/nginx.co... ok
Nov 07 13:51:50 inspiredev nginx[30558]: nginx: configuration file /etc/nginx/nginx.conf t...ful
Nov 07 13:51:50 inspiredev systemd[1]: Failed to read PID from file /run/nginx.pid: Invali...ent
Nov 07 13:51:50 inspiredev systemd[1]: Started The nginx HTTP and reverse proxy server.

trying to install nvm on Fedora from app user account, but I ran into this issue:

=> Downloading nvm from git to '/root/.nvm'
=> mkdir: cannot create directory ‘/root’: Permission denied

Do I need to use sudo for this?

I don’t have “grunt deploy” command, is it an additional grunt plugin and which?

On Debian 7 (Wheezy) I had to create the user like this:

useradd -mrU -s /bin/bash srv-node-sample

and then the curl command should be:

curl https://raw.github.com/creationix/nvm/master/install.sh | bash

Also, for whoever is following this tutorial using Debian 7, you may want to install systemd from wheezy-backports. The version from the main repository does not have journalctl.

Thanks @mpaeschke! I was missing that config option too. Very useful tutorial otherwise :)

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more