Vladislav Yarmak

Making Services With Go Right Way

Modern backend development is overly complex. In order to deploy even simplest Golang web service now you’d probably going to need:

Go makes many things straightforward, yet there’s still a lot of unnecessary complexity. Some companies even have dedicated “platform engineering” teams to deal with that, which speaks volumes about the issue.

Web services with dynamic content is a problem solved long time ago. Does it really need to be that complex today? Is there easier way? I think, there is.

The Right Way

The way I’m suggesting is better explained with illustrated example, going through a complete step-by-step guide.

Step 1. Get yourself a VPS

Just get a Linux server. In this guide I use one with Ubuntu 24.04.

Step 2. Install web server

Let’s go with tried and true Apache 2.

apt update && apt install -y apache2
a2enmod cgid

Edit file /etc/apache2/conf-enabled/serve-cgi-bin.conf and make sure it looks like this:

<IfModule mod_alias.c>
	<IfModule mod_cgi.c>
		Define ENABLE_USR_LIB_CGI_BIN
	</IfModule>

	<IfModule mod_cgid.c>
		Define ENABLE_USR_LIB_CGI_BIN
	</IfModule>

	<IfDefine ENABLE_USR_LIB_CGI_BIN>
		ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
		<Directory "/usr/lib/cgi-bin">
			AllowOverride None
			SetEnv GOCACHE /var/tmp/.www-cache/go-build
			Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
			Require all granted
		</Directory>
	</IfDefine>
</IfModule>

Note the added line SetEnv GOCACHE /var/tmp/.www-cache/go-build.

Finally, restart the web server to apply configuration:

systemctl restart apache2

Step 3. Setup support for Go scripts

First we need Golang itself.

apt install -y golang-go

Then we need a binfmt support to enable Golang scripts:

go install github.com/Snawoot/go-binfmt@latest
install /root/go/bin/go-binfmt /usr/local/bin
cat > /etc/systemd/system/go-binfmt.service <<'EOF'
[Unit]
Description=Register go-binfmt support
After=proc-sys-fs-binfmt_misc.mount
Wants=proc-sys-fs-binfmt_misc.mount

[Service]
Type=oneshot
ExecStart=/usr/local/bin/go-binfmt -register
ExecStop=/usr/local/bin/go-binfmt -unregister
RemainAfterExit=yes
User=root

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now go-binfmt.service

Step 4. Code!

Let’s greet the world with a simple “Hello World” Go script!

Create /usr/lib/cgi-bin/hello.go with following content:

package main

import (
	"fmt"
	"net/http"
	"net/http/cgi"
)

func main() {
	err := cgi.Serve(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		fmt.Fprintln(w, "<!DOCTYPE html>")
		fmt.Fprintln(w, "<html><body><head><title>Hello World</title></head>")
		fmt.Fprintln(w, "<h1 style=\"color: green;\">Hello, World!</h1>")
		fmt.Fprintln(w, "<p>This page was generated by a Go script running on Apache2 (Ubuntu 24.04).</p>")
		fmt.Fprintln(w, "</body><html>")
	}))
	if err != nil {
		fmt.Println(err)
	}
}

And make this script executable:

chmod +x /usr/lib/cgi-bin/hello.go

That’s it! Now your web application is accessible on URL http://SERVER_IP/cgi-bin/hello.go.

Advantages of This Approach