Go Development

Running Go Applications on Domain India VPS (systemd, nginx Reverse Proxy, SSL)

By Domain India Team · DomainIndia EngineeringPublished 8 min read
Knowledge base article
Contents (13 sections)

A Go web service is one compiled binary that listens on a port. Getting it into production on a Linux VPS takes five steps, and this guide covers each with copy-ready config.

Key takeaways

Go (Golang) produces a single compiled binary — ideal for lean production deployments on a Domain India VPS. Build the binary, run it as a systemd service under its own user, put nginx or Caddy in front of it on ports 80/443, and add a free Let's Encrypt certificate. Shared hosting cannot run your own long-running binary, so Go needs a VPS or the App Platform with a Dockerfile.

Why Go on a VPS (and not shared)

Go apps are long-running compiled processes listening on a port. Domain India shared hosting (cPanel, DirectAdmin, Webuzo) runs PHP sites, plus Node.js and Python apps through the panel tools on cPanel and DirectAdmin; it does not run your own long-running binaries, and processes outside the web server are stopped. A VPS gives you systemd, port control and your own firewall.

Any Go app — REST API, WebSocket server, gRPC service, scheduled job — runs the same way on a VPS:

  1. Compile to a binary on your laptop (or on the VPS)
  2. Upload the binary
  3. Run it as a systemd service
  4. Proxy HTTP via nginx/Caddy
  5. Get SSL via Let's Encrypt

Step 1 — Prepare the VPS

Order a Domain India VPS and pick a current Linux release (the examples use AlmaLinux 9 and Ubuntu 24.04 LTS). SSH in as root, and harden SSH first with the SSH security checklist:

  1. Create a non-root user for the app:
    bash
    useradd -m -s /bin/bash goapp
    mkdir -p /home/goapp/app/data
    chown -R goapp:goapp /home/goapp/app
  2. Install essentials:
    bash
    # AlmaLinux (certbot comes from EPEL)
    sudo dnf install -y epel-release
    sudo dnf install -y nginx firewalld certbot python3-certbot-nginx
    sudo systemctl enable --now nginx firewalld
    sudo firewall-cmd --permanent --add-service=http --add-service=https
    sudo firewall-cmd --reload
    # Ubuntu
    sudo apt install -y nginx ufw certbot python3-certbot-nginx
    sudo ufw allow OpenSSH && sudo ufw allow 'Nginx Full' && sudo ufw enable
  3. Install Go (optional — only if you'll compile on the server):
    bash
    # Use the current stable version listed on go.dev/dl
    GO_VERSION=1.xx.x
    wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
    sudo rm -rf /usr/local/go
    sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
    echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh
    source /etc/profile.d/go.sh
    go version

Step 2 — Build your binary

On your laptop (recommended) — cross-compile for Linux:

bash
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o myapp ./cmd/server

Upload:

bash
scp myapp goapp@your-vps-ip:/home/goapp/app/

Or build on the VPS:

bash
cd /home/goapp/app
git clone https://github.com/yourcompany/myapp .
go build -o myapp ./cmd/server
Insight

Keep the binary small. CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" strips debug symbols (often about a quarter to a third smaller) and removes local file paths from the binary.

Step 3 — Run under systemd

Create /etc/systemd/system/myapp.service:

ini
[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=goapp
Group=goapp
WorkingDirectory=/home/goapp/app
ExecStart=/home/goapp/app/myapp
Restart=on-failure
RestartSec=5

# Environment: keep secrets such as DATABASE_URL in the .env file
# (chmod 600, owned by goapp), not in the unit file, which any user can read
Environment="PORT=8080"
EnvironmentFile=-/home/goapp/app/.env

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/goapp/app/data

# Logging
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

ReadWritePaths must point at a folder that exists (created in Step 1), or the service fails to start. Start and enable:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Check logs:

bash
sudo journalctl -u myapp -f

Step 4 — nginx reverse proxy

Go listens on port 8080 (internal). nginx takes port 80/443 public traffic and forwards it.

/etc/nginx/conf.d/myapp.conf:

nginx
upstream myapp_backend {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    server_name api.yourcompany.com;

    # Redirect to HTTPS after SSL setup
    # return 301 https://$host$request_uri;

    location / {
        proxy_pass http://myapp_backend;
        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 300s;
    }
}

Test and reload:

bash
sudo nginx -t && sudo systemctl reload nginx

Step 5 — Let's Encrypt SSL

bash
sudo certbot --nginx -d api.yourcompany.com

Certbot modifies your nginx config to add:

  • SSL certificate paths
  • HTTP → HTTPS redirect

The certbot package also installs a systemd timer that renews certificates automatically.

Verify renewal:

bash
sudo certbot renew --dry-run

Alternative: Caddy (much simpler)

Caddy auto-obtains Let's Encrypt SSL and auto-renews. /etc/caddy/Caddyfile:

code
api.yourcompany.com {
    reverse_proxy 127.0.0.1:8080
}

That's it. Run sudo systemctl reload caddy; Caddy requests the certificate as soon as the domain's DNS points at the VPS and ports 80 and 443 are open.

See our Nginx vs Caddy comparison article for the trade-offs.

Zero-downtime deployments

For a production API, deploying via "stop → replace binary → start" causes a few seconds of downtime. Two better approaches:

Approach 1 — Graceful restart with SIGHUP:

Use a package like github.com/cloudflare/tableflip: the old process keeps serving existing requests while the new one picks up new connections. Whatever you choose, handle SIGTERM in your app and call http.Server.Shutdown so in-flight requests finish before the process exits.

Approach 2 — Blue-green with systemd:

Run two services (myapp-blue.service + myapp-green.service) on different ports. nginx upstream points to the active one. To deploy:

bash
# Stop green, deploy new binary, start green
sudo systemctl stop myapp-green
cp new-binary /home/goapp/app/myapp-green
sudo systemctl start myapp-green

# Wait for green to become healthy
curl -f http://127.0.0.1:8081/health

# Switch nginx to green, reload
sudo sed -i 's/server 127.0.0.1:8080/server 127.0.0.1:8081/' /etc/nginx/conf.d/myapp.conf
sudo nginx -s reload

Database access

PostgreSQL (recommended for Go):

bash
# AlmaLinux
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
# Ubuntu: sudo apt install -y postgresql
sudo -u postgres createdb myapp
sudo -u postgres createuser goapp --pwprompt

Connection string in .env:

code
DATABASE_URL=postgres://goapp:password@localhost/myapp?sslmode=disable

MySQL or MariaDB: install the server package from your distribution and use github.com/go-sql-driver/mysql.

SQLite: fine for small apps, zero setup. github.com/mattn/go-sqlite3 needs cgo, which clashes with CGO_ENABLED=0; the pure-Go modernc.org/sqlite driver works with it.

Monitoring

Expose a Prometheus /metrics endpoint in your Go app:

go
import "github.com/prometheus/client_golang/prometheus/promhttp"
http.Handle("/metrics", promhttp.Handler())

On the server, install Prometheus and Grafana, or send metrics to a hosted service such as Grafana Cloud (check its free-tier limits on its pricing page). Don't expose /metrics publicly: block it in nginx or serve it on a separate internal port.

Common pitfalls

Binary won't run: "exec format error"
Cross-compile target mismatch: you built for arm64 (Apple Silicon default) and the VPS is amd64. Set GOARCH=amd64 explicitly.
"bind: address already in use"
A previous process still holds the port. Run sudo systemctl stop myapp, then sudo ss -tlnp to find the holder.
Killed in production with SIGKILL
The OOM killer on a small VPS. Set GOMEMLIMIT (for example Environment="GOMEMLIMIT=256MiB") to give the runtime a memory budget.
Certificate request fails
The domain's DNS does not point at the VPS yet, port 80 is closed, or you hit a Let's Encrypt rate limit. Fix DNS and the firewall first, then retry; use --dry-run while testing.
nginx returns 502 Bad Gateway
The Go app crashed or isn't listening. Check sudo journalctl -u myapp -n 100.

Running this on Domain India

  • VPS: self-managed KVM with full root access, so everything above applies. There is no VPS page or console in the client area; you reboot from inside over SSH, and for a console, reinstall or resize you open a support ticket. Backups and snapshots are not included.
  • App Platform: deploys from GitHub (Deploy Now) or with a deploy token. Only Node.js is detected automatically, so add a multi-stage Dockerfile that builds your Go binary. PostgreSQL and free SSL are included on every plan; WebSockets and SSH are not available.
  • Shared hosting: not suitable for Go services.

FAQ

Can I run Go on Domain India shared hosting?

No. Shared hosting does not run your own long-running binaries or let you bind ports. Use a VPS, or the App Platform with your own Dockerfile (Go is not detected automatically there).

How much RAM do I need?

Go itself is memory-efficient, and a small API often runs in well under 100 MB. Size the server for everything on it, especially PostgreSQL or MySQL, and measure your app under realistic load before choosing.

Should I use gin, echo, fiber or chi?

Since Go 1.22 the standard library router supports methods and path parameters, so net/http alone covers many APIs. chi adds middleware and route groups while staying net/http-compatible; gin and echo are full-featured; fiber is built on fasthttp and does not use net/http handlers.

How do I update Go without breaking my running app?

Compile the binary with the new Go version, drop it in, systemctl restart myapp. The Go runtime is statically linked — the Go version on disk doesn't matter to an already-compiled binary.

How much does a Domain India VPS cost for a Go app?

Domain India VPS plans start from ₹553 a month excluding GST. They are self-managed KVM servers with full root access; backups and snapshots are not included, so back up your own data. Compare plans on the VPS page.

Ready to deploy Go? Compare VPS plans, or package your binary with a Dockerfile for the App Platform.

Deploy your Go app

A self-managed Domain India VPS gives you full root access for systemd, nginx and your database.

View VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Run Go Apps on a VPS: systemd, nginx and SSL | Domain India