Building a Go web application and getting it running in production are two different challenges. Go compiles to a single static binary that is trivial to deploy, but you still need a proper project structure, a way to manage runtime configuration securely, a systemd service for process supervision, and a reverse proxy to handle TLS and route public traffic. In this tutorial you will build a Go web application using both the standard library and the popular Gin framework, compile it as a hardened production binary, deploy it as a systemd service, and front it with Nginx.

Prerequisites

  • RHEL 8 with sudo access and Go 1.21 or newer installed — see the Go installation tutorial for setup steps
  • Nginx installed (sudo dnf install -y nginx)
  • Firewall port 80 open (sudo firewall-cmd --permanent --add-service=http && sudo firewall-cmd --reload)
  • Basic familiarity with Go modules and the Linux command line

Step 1 — Create the Go Project and Initialize a Module

Start by creating a clean project directory and initializing a Go module. Using a module path in the form of a domain name is conventional even for private projects:

mkdir -p ~/mygoapp && cd ~/mygoapp
go mod init example.com/mygoapp

Add the Gin framework as a dependency. Gin provides a router with path parameters, middleware support, and JSON helpers that significantly reduce boilerplate compared to the standard library:

go get github.com/gin-gonic/gin

This updates go.mod and creates go.sum with the cryptographic checksums of all downloaded modules. Both files should be committed to version control.

Step 2 — Write the Web Application

Create the main application file. This example reads the listen port from an environment variable and exposes two routes — a root endpoint and a JSON API endpoint:

cat > main.go << 'EOF'
package main

import (
    "log"
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
)

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    r := gin.Default()

    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello from Go + Gin on RHEL 8!")
    })

    r.GET("/api/status", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "status":  "ok",
            "service": "mygoapp",
        })
    })

    log.Printf("Starting server on port %s", port)
    if err := r.Run(":" + port); err != nil {
        log.Fatalf("Server failed: %v", err)
    }
}
EOF

Using os.Getenv to read configuration keeps secrets out of the source code and allows the same binary to be configured differently across environments without recompilation.

Step 3 — Build a Production Binary

For production deployment, compile with CGO disabled and strip debug information to produce the smallest, most portable binary possible:

CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o mygoapp .
ls -lh mygoapp
file mygoapp

CGO_ENABLED=0 creates a fully static binary with no C library dependencies, so it runs on any Linux/amd64 host regardless of the installed libc version. The -s -w linker flags strip the symbol table and DWARF debugging information, typically reducing binary size by 25–30%. Run a quick local test:

PORT=8080 ./mygoapp &
curl http://localhost:8080/
curl http://localhost:8080/api/status
kill %1

Step 4 — Create a Service User and Deploy the Binary

Never run a public-facing service as root. Create a system user and deploy the binary to a standard location:

sudo useradd -r -s /sbin/nologin goapp
sudo mkdir -p /opt/mygoapp
sudo cp mygoapp /opt/mygoapp/mygoapp
sudo chown -R goapp:goapp /opt/mygoapp

# Create an environment file for runtime configuration
sudo tee /opt/mygoapp/mygoapp.env << 'EOF'
PORT=8080
GIN_MODE=release
EOF

sudo chown root:goapp /opt/mygoapp/mygoapp.env
sudo chmod 640 /opt/mygoapp/mygoapp.env

Setting GIN_MODE=release suppresses Gin’s debug output in production and disables the development warning banner.

Step 5 — Create the systemd Service

Write a systemd unit file that loads the environment file, runs under the dedicated user, and restarts the service automatically if it crashes:

sudo tee /etc/systemd/system/mygoapp.service << 'EOF'
[Unit]
Description=My Go Web Application
After=network.target

[Service]
Type=simple
User=goapp
Group=goapp
WorkingDirectory=/opt/mygoapp
EnvironmentFile=/opt/mygoapp/mygoapp.env
ExecStart=/opt/mygoapp/mygoapp
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/opt/mygoapp

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now mygoapp
sudo systemctl status mygoapp

The NoNewPrivileges and ProtectSystem directives are systemd hardening options that prevent the process from escalating privileges or writing to system directories outside its designated path.

Step 6 — Configure Nginx as a Reverse Proxy

Place Nginx in front of the Go application to handle TLS termination in the future, set proper forwarding headers, and allow you to serve static files directly without involving the Go process:

sudo tee /etc/nginx/conf.d/mygoapp.conf << 'EOF'
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}
EOF

sudo nginx -t
sudo systemctl enable --now nginx
curl http://localhost/
curl http://localhost/api/status

Replace example.com with your domain. After confirming everything works over HTTP, use Certbot to add a Let’s Encrypt TLS certificate with a single command.

Conclusion

You have built a Go web application with the Gin framework, compiled it as a hardened static binary using CGO_ENABLED=0 and stripped linker flags, deployed it under a least-privilege system user with a secure environment file, managed it with a hardened systemd unit, and exposed it through an Nginx reverse proxy. The resulting deployment is portable, fast to start, and straightforward to update — simply build a new binary, copy it to /opt/mygoapp/mygoapp, and run sudo systemctl restart mygoapp.

Next steps: How to Install Go on RHEL 8, How to Secure Nginx with Let’s Encrypt on RHEL 8, and How to Deploy a Spring Boot Application on RHEL 8.