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 AMAmir 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 PMAndrew 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 AMR2D2 liked this post.
-
Sep 5 2012, 6:28 AMMr_M7mD (Twitter) liked this post.
-
Mar 20 2013, 11:36 AMChuck 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?