Deploying Go web services behind Nginx under Debian or Ubuntu

A couple of days ago I wrote a simple URL shortening service named Goto for my own personal use. I deployed it on a Virtual Private Server that I rent from prgmr.com. The server already had an Apache installation serving some simple web sites, so I had to find a way to continue to serve them while serving requests to the new Goto service.

One option is to get another IP address from my VPS provider and use that for serving Goto requests. But, being the responsible netizen that I am, I’d rather not contribute to IPv4 address exhaustion without good cause. Another option is to run Goto on a port other than 80, but that would add at least a few more characters every URL, defeating the purpose of a URL shortening service.

The third option is to use named virtual hosts. Practically all browsers send a Host header in their HTTP requests that specifies the hostname the request is destined for. This enables a web server to serve many sites on the same IP address and port. To run Goto on the same port as my other web sites, I’d need to configure the web server to forward requests with a specific Host to the Goto service.

The first step was to throw Apache out. It was already consuming more than half the VPS’s available RAM; complete overkill for serving simple static web pages. In its stead, I installed the wonderful Russian-born Nginx web server (as root):

$ /etc/init.d/apache2 stop      # uninstall apache2
$ update-rc.d -f apache2 remove # disable apache2
$ apt-get install nginx         # install nginx

After familiarising myself with Nginx’s simple configuration language, I had the web server back up and running in a few minutes (and using a mere 900kb of memory, a stark contrast to Apache’s >100mb).

(Admittedly, this would have been trickier if my original Apache set-up were more complex – involving PHP, for example. In that case I may have opted to put both Apache and Goto behind an Nginx installation.)

Here’s how to configure Nginx to proxy requests to the Goto service:

Goto runs as a stand-alone web server. Tell it to listen on 127.0.0.1:9980. Listening on localhost prevents outsiders from connecting to it directly. (The specific port number was chosen arbitrarily.)

$ goto -http=127.0.0.1:9980

Configure Nginx to forward requests to the service. Do this by creating a site configuration file under /etc/nginx/sites-available. In this case I named it goto. This configuration forwards all requests to r.nf.id.au to the Goto service running at http://127.0.0.1:9980:

server {
    listen 80;
    server_name r.nf.id.au;
    access_log /var/log/nginx/goto.access.log;
    location / {
        proxy_pass http://127.0.0.1:9980;
    }       
}

Enable the site configuration by creating a symlink to it in sites-enabled, and telling Nginx to reload (as root):

$ cd /etc/nginx/sites-enabled
$ ln -s ../sites-available/goto
$ /etc/init.d/nginx reload

You should have already modified your DNS settings to point the hostname to the web server. (Sometimes DNS changes can take a while to propagate; you might want to add a line to your local /etc/hosts to set the hostname manually for the time being.) Check that this worked by loading the URL in a web browser.

We’ve now accomplished what we set out to do. But what if the machine restarts? We need to configure the system to run the goto process on start-up.

Debian makes this pretty straightfoward with its init.d system. You could write the init.d script yourself (if you know how), but its simpler to base it on an existing script. Create a copy of /etc/init.d/nginx, name it /etc/init.d/goto, and modify it to manage the Goto program instead:

#!/bin/sh

### BEGIN INIT INFO
# Provides:          goto
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the goto server
# Description:       starts goto using start-stop-daemon
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin

BIN=/usr/sbin/goto
PIDFILE=/var/run/goto.pid
USER=nobody
GROUP=nogroup

HOST=r.nf.id.au
HTTP=127.0.0.1:9980
FILE=/var/spool/goto/store.gob
PASS=password
BINARGS="-host=$HOST -http=$HTTP -file=$FILE -pass=$PASS"

test -f $BIN || exit 0
set -e
case "$1" in
  start)
    echo -n "Starting goto server: "
    start-stop-daemon --start --chuid $USER:$GROUP \
        --make-pidfile --background --pidfile $PIDFILE \
        --exec $BIN -- $BINARGS
    echo "goto."
    ;;
  stop)
    echo -n "Starting goto server: "
    start-stop-daemon --stop --quiet --pidfile $PIDFILE --exec $BIN
    rm -f $PIDFILE
    echo "goto."
    ;;
  restart)
    echo -n "Restarting goto server: "
    $0 stop
    sleep 1
    $0 start
    echo "goto."
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}" >&2
    exit 1
    ;;
esac
exit 0

There’s some configuration information in there (hostname, http port, etc). It’s not considered a good practice to put that kind of information in an init.d script, but it’ll do for now. (The alternatives are to modify Goto to read from a configuration file, or to have the init.d script call another shell script – named something like /etc/goto.conf – to set the environment variables.)

The data file is to be stored in /var/spool/goto, and, as a security precaution, the goto process runs as user nobody in group nogroup. Create the directory and set its ownership appropriately:

$ mkdir /var/spool/goto
$ chown nobody.nogroup /var/spool/goto

Start the service using the init.d script:

$ /etc/init.d/goto start
Starting goto server: goto.

Finally, add the service to rc.d so that it is launched on start-up:

$ update-rc.d goto defaults
 Adding system startup for /etc/init.d/goto ...
   /etc/rc0.d/K20goto -> ../init.d/goto
   /etc/rc1.d/K20goto -> ../init.d/goto
   /etc/rc6.d/K20goto -> ../init.d/goto
   /etc/rc2.d/S20goto -> ../init.d/goto
   /etc/rc3.d/S20goto -> ../init.d/goto
   /etc/rc4.d/S20goto -> ../init.d/goto
   /etc/rc5.d/S20goto -> ../init.d/goto

And that’s it, for now. There is more you could do in the way of logging and monitoring, but these are the bare essentials to reliably run a Go (or any other) web service behind Nginx under Debian.

61317 views and 5 responses

  • Oct 4 2010, 5:47 AM
    Amir responded:
    Something completely unrelated to this post, but to Goto.

    Ain't the generated IDs in base62, and not base36? (At least having a look at the source code the radix is 62, but named 36)

  • Oct 4 2010, 3:44 PM
    Andrew Gerrand responded:
    You're right. I had added the uppercase letters to the character set as an afterthought, but hadn't changed the function name (or the radix). Thanks for pointing it out! =D
  • Aug 23 2011, 3:12 AM
    R2D2 liked this post.
  • Sep 5 2012, 6:28 AM
    Mr_M7mD (Twitter) liked this post.
  • Mar 20 2013, 11:36 AM
    Chuck Han responded:
    I am able to deploy multiple Go web apps that need to be configured with different localhost ports using your methodology (THANKS!!!). Taking up those ports isn't necessary when, say, deploying multiple Sinatra apps using Nginx/Passenger. I'm wondering if there's a way to deploy multiple Go web apps without taking up a port for each app?