Using the Node.js https client package and systemd timers to create a simple health check for a website

The http and https packages are most commonly used in Node.js to create an HTTP(S) server. The same packages can also be used to create an asynchronous HTTP(S) client. In this article we will use the http and https package's server and client capabilities to create a very simple health probe for a URL.

The example will consist of:

  • a Node.js application that combines HTTP(S) client and server capabilities to implement a simple health probe.
  • a systemd service to call the nodejs application using cURL.
  • a systemd timer to trigger the service every 15 minutes.

Step 1 - Create the probe application with Node.js

For this step you will need two files:

  • probe.js - the node application
  • package.json - a package for the dependencies so that you can run probe.js with npm start

Our application, probe.js, simply starts up an Express application that listens for http requests on port 3000. A path to /trigger is set up that will in turn call our probe(options, expected) function with an options object an the expected statusCode.

'use strict';
const https = require('https');
const http = require('http');
var express = require('express');

// Constants
const PORT = 3000;

// App
const app = express();

function probe(options, expected) {

    // set up an https request with the provided options
    var req = https.request(options);
    // send the request
    req.end();

    // when we get the response, check the statusCode
    req.on('response', (res) => {
        // if the status code is the expected value provided then the probe is OK otherwise ALERT
        if (res.statusCode === expected) {
            // write "OK" to the log
            console.log("OK");
        } else {
            // write "ALERT" to the log
            console.log("ALERT")
        }
    });
}

// responds to GET requests to /trigger
app.get('/trigger', function (req, res) {

    // set up the options that we will send to the probe function
    // port 443 -- https
    // hostname -- the host (www.example.com)
    // path - the path
    // resulting URL: https://www.example.com/
    var options = {
        port : 443,
        hostname: 'www.example.com',
        path: "/"
    };

    // call the probe function with the options and the expected statusCode
    probe(options, 200);

    // send a response back to the caller so that the service doesn't fail
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');    
    res.end("OK");
});

// listen on port 3000
app.listen(PORT);
console.log('Running on http://localhost:' + PORT);

In this example when the HTTPS client request to https://www.example.com/ returns a statusCode other than 200, we will write ALERT to the log. In a realworld example, you will want to do something more actionable, such as sending an email, SMS or firing a message into a message queue.

So that we can start the Node.js application with the npm start command, we will need a package.json file that includes Express as a dependency and runs a prestart and start script to get everything in place.

{
  "name": "httpclient-probe",
  "version": "0.1.0",
  "description": "An http client probe",
  "main": "probe.js",
  "scripts": {
    "prestart": "npm install",
    "start": "node probe.js"
  },
  "dependencies": {
    "express": "^4.13.3",
  }
}

Step 2: Create a systemd service to call the Node.js application

After completing the first step, you have a working health probe for https://www.example.com/. You can test it using cURL:

> curl http://localhost:3000/trigger

This will write OK to the log if https://www.example.com/ returns a 200 statusCode or ALERT otherwise.

We will incorporate this into a systemd one-shot service that can then be scheduled.

The service is given a Type of oneshot and an ExecStart using cURL and the url for the Node.js application.

[Unit]
Description=Probe for https://www.example.com/

[Service]
Type=oneshot
ExecStart=/usr/bin/curl http://127.0.0.1:3000/trigger

This will work on any environment that uses systemd (such as Ubuntu 16.04 or CoreOS). Save this file as probe.service in /etc/systemd/system/.

You can test this using the command:

> sudo systemctl daemon-reload
> sudo systemctl start probe.service

After running these commands you can check the log of your Node.js application to see either OK or ALERT.

If the service doesn't run as you expected, you can find out more information about why by using the command:

> systemctl status probe.service

Step 3: Create a systemd timer to run probe.service every 15 minutes

We want to run probe.service every 15 minutes, so let's create a systemd timer.

Create a file called probe.timer in /etc/systemd/system/. Note that the timer and the service should have the same name (probe.service and probe.timer). This is a systemd convention that makes the relationship between a timer and service very clear.

This timer will run probe.service every 15 minutes once the timer is started.

[Unit]
Description=Run probe.service every 15 minutes

[Timer]
OnCalendar=*:0/15

[Install]
WantedBy=multi-user.target

The OnCalendar value of *:0/15 specifies that the timer runs every day and every 15 minutes (0/15). For more examples and information on this date notation refer to documentation for systemd.time.

To start the timer use the commands:

> sudo systemctl daemon-reload
> sudo systemctl enable probe.timer
> sudo systemctl start probe.timer

Conclusion

This example combines Node.js HTTP(S) client and server functionalities with systemd services and timers using cURL as an intermediary. This is a very simple example of a website health check. For simpler monitoring needs, this may be sufficient. For more complex monitoring needs, you should look to services such as Pingdom or a solution like the excellent Prometheus.