Technology Encyclopedia Home >How to Deploy a Go Web App on a Cloud Server — Build, Run, and Serve with Nginx

How to Deploy a Go Web App on a Cloud Server — Build, Run, and Serve with Nginx

The first time I deployed a Go application, I remember thinking: is this it? Cross-compile on my Mac, scp the binary to the server, run it. That's genuinely the whole process.

Coming from Python and Node.js where deployment always involves installing a runtime, managing package versions, and setting up virtual environments — Go's single-binary model felt almost suspicious. Surely there's a catch.

There isn't, really. The binary includes everything it needs. No runtime dependencies, no version mismatches, no requirements.txt. This makes Go deployments unusually clean and portable.

This guide covers the complete setup: building the binary, transferring it, running it with systemd, and putting Nginx in front with HTTPS.

I run this on Tencent Cloud Lighthouse. A Go binary is unusually efficient with server resources — a typical Go API uses 20–50 MB of RAM at idle, which means even the entry-level Lighthouse plan (2 GB RAM) has enormous headroom. This is one use case where a smaller, cheaper server plan works remarkably well. I also appreciate that Lighthouse instances provision in under 2 minutes — Go's single-binary deployment model means I can set up a new server and have the app running in 5–10 minutes total, including Nginx configuration.


Table of Contents

  1. Why Go Is Unusually Easy to Deploy
  2. Prerequisites
  3. Part 1 — Set Up the Server
  4. Part 2 — Install Go (for building on server) or Build Locally
  5. Part 3 — Deploy Your Go Application
  6. Part 4 — Run the App with systemd
  7. Part 5 — Configure Nginx as a Reverse Proxy
  8. Part 6 — Enable HTTPS
  9. Part 7 — Cross-Compile and Upload (Build Locally)
  10. The Gotcha: Binding to Port 80 Directly
  11. Auto-Deploy Workflow

Key Takeaways

  • Cross-compile with GOOS=linux GOARCH=amd64 go build — no runtime needed on server
  • Go binaries use very little memory at idle — suitable for even the smallest plans
  • Use systemd Environment= for passing config to the binary
  • Implement graceful shutdown with http.Server.Shutdown() for zero-downtime restarts
  • Build with -ldflags="-s -w" to strip debug symbols and reduce binary size

Why Go Is Unusually Easy to Deploy {#why-go}

Most languages have a deployment friction point:

  • Node.js: install the right Node version, install npm packages, keep the process running
  • Python: virtual environments, multiple Python versions, WSGI server needed
  • Ruby: rbenv, bundle install, separate application server
  • PHP: PHP-FPM configuration, PHP extensions

Go has almost none of this. You go build, get a single binary, copy it to the server, and run it. The binary contains everything it needs to run.

# On your local machine
GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/server

# Copy to server
scp myapp ubuntu@YOUR_SERVER_IP:~/apps/

# On the server
./myapp
# Running. No setup required.

This makes Go an excellent choice when you want minimal operational complexity.


Prerequisites {#prerequisites}

Requirement Notes
Cloud server Tencent Cloud Lighthouse Ubuntu 22.04
Go app A basic HTTP server works; we'll provide a minimal example
Domain name (optional) For HTTPS

Part 1 — Set Up the Server {#part-1}

ssh ubuntu@YOUR_SERVER_IP
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx git

sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable

Part 2 — Install Go (for building on server) or Build Locally {#part-2}

You have two options: build on the server, or cross-compile locally and upload the binary.

Option A: Install Go on the server

# Download the latest Go release (check golang.org/dl for current version)
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz

# Extract to /usr/local
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz

# Add to PATH
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

# Verify
go version

Option B: Cross-compile on your local machine (recommended)

Build and upload — no Go installation needed on the server:

# On your local machine
GOOS=linux GOARCH=amd64 go build -o myapp-linux ./cmd/server

# Upload
scp myapp-linux ubuntu@YOUR_SERVER_IP:~/apps/myapp

Part 3 — Deploy Your Go Application {#part-3}

Example: minimal Go HTTP server

If you want to follow along without an existing app:

mkdir -p ~/apps/mygoapp && cd ~/apps/mygoapp

Create main.go:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
)

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

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "<h1>Go app running on Tencent Cloud Lighthouse</h1>")
    })

    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    log.Printf("Starting server on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Build and test

cd ~/apps/mygoapp
go build -o myapp .

# Test it
PORT=8080 ./myapp
# Visit http://YOUR_SERVER_IP:8080
# Ctrl+C to stop

Part 4 — Run the App with systemd {#part-4}

Create a systemd service to keep the app running:

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

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/apps/mygoapp
ExecStart=/home/ubuntu/apps/mygoapp/myapp
Restart=on-failure
RestartSec=5s

# Environment variables
Environment="PORT=8080"
Environment="GIN_MODE=release"

# Optional: load from env file
# EnvironmentFile=/home/ubuntu/apps/mygoapp/.env

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start mygoapp
sudo systemctl enable mygoapp
sudo systemctl status mygoapp

Check logs:

sudo journalctl -u mygoapp -f

Part 5 — Configure Nginx as a Reverse Proxy {#part-5}

sudo nano /etc/nginx/sites-available/mygoapp
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    access_log /var/log/nginx/mygoapp_access.log;
    error_log  /var/log/nginx/mygoapp_error.log;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
        proxy_read_timeout 300s;
    }

    # Serve static files directly if you have a static directory
    location /static/ {
        root /home/ubuntu/apps/mygoapp;
        expires 30d;
    }
}
sudo ln -s /etc/nginx/sites-available/mygoapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Part 6 — Enable HTTPS {#part-6}

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Part 7 — Cross-Compile and Upload (Build Locally) {#part-7}

This is the workflow I use for most Go deployments. Build the binary locally (much faster than building on the server), upload it, and restart the service:

# On your local machine
# Build for Linux AMD64 (what most cloud servers run)
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o myapp-linux ./cmd/server

# The -ldflags="-s -w" strips debug info, making the binary smaller
# CGO_ENABLED=0 ensures a static binary with no C dependencies

# Upload
scp myapp-linux ubuntu@YOUR_SERVER_IP:~/apps/mygoapp/myapp.new

# On the server: swap binaries and restart
ssh ubuntu@YOUR_SERVER_IP
cd ~/apps/mygoapp
mv myapp.new myapp
sudo systemctl restart mygoapp

This deploys a new version in about 10 seconds.


The Gotcha: Binding to Port 80 Directly {#gotcha}

Go makes it tempting to bind directly to port 80 (http.ListenAndServe(":80", nil)). On Linux, binding to ports below 1024 requires root privileges.

The right approach: run Go on a non-privileged port (8080 or similar) and let Nginx handle port 80/443. This is the setup in this guide.

If you want Go to handle port 80 directly without Nginx, you can grant the binary the capability:

sudo setcap 'cap_net_bind_service=+ep' /home/ubuntu/apps/mygoapp/myapp

But using Nginx as a reverse proxy is generally better — it handles SSL, compression, static files, and rate limiting more efficiently.


Auto-Deploy Workflow {#deployment}

Create a deploy script on the server:

nano ~/deploy-mygoapp.sh
#!/bin/bash
set -e

APP_DIR=~/apps/mygoapp
BINARY=myapp

echo "Deploying Go app..."

# If building on server
# cd $APP_DIR && git pull && go build -o $BINARY .

# If uploading pre-built binary:
# (scp handles this before this script runs)

sudo systemctl restart mygoapp
echo "Deployed successfully."

Combine with GitHub Actions for a full CI/CD pipeline — build the binary in Actions, SCP it to the server, then SSH and restart the service.

Troubleshooting {#troubleshooting}

Issue Likely Cause Fix
Connection refused Service not running or wrong port Check systemctl status SERVICE and verify firewall rules
Permission denied Wrong file ownership or permissions Check file ownership with ls -la and use chown/chmod to fix
502 Bad Gateway Backend service not running Restart the backend service; check logs with journalctl -u SERVICE
SSL certificate error Certificate expired or domain mismatch Run sudo certbot renew and verify domain DNS points to server IP
Service not starting Config error or missing dependency Check logs with journalctl -u SERVICE -n 50 for specific error
Out of disk space Logs or data accumulation Run df -h to identify usage; clean logs or attach CBS storage
High memory usage Too many processes or memory leak Check with htop; consider upgrading instance plan if consistently high
Firewall blocking traffic Port not open in UFW or Lighthouse console Open port in Lighthouse console firewall AND sudo ufw allow PORT

Frequently Asked Questions {#faq}

How do I cross-compile a Go binary for Linux from macOS?
Set GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/server on your Mac. The resulting binary runs on any Linux/amd64 server without any Go runtime installed.

Does a Go binary need any runtime dependencies on the server?
No — Go compiles to a fully self-contained binary with no runtime dependencies. Copy the binary to the server and run it directly.

How do I check if my Go app has any memory leaks in production?
Go includes built-in profiling. Enable the net/http/pprof package and access http://localhost:6060/debug/pprof/ for heap profiles, goroutine counts, and other metrics.

What is the best way to manage environment configuration in a Go app?
Use environment variables loaded via os.Getenv() or the godotenv package for .env file support. Set them in the systemd service file using Environment=KEY=value directives.

How do I handle graceful shutdown in Go for zero-downtime deployments?
Use http.Server.Shutdown(ctx) with a context that has a timeout. This finishes in-flight requests before stopping. Pair with systemd's ExecStop for proper signal handling.

Deploy your Go app today:
👉 Tencent Cloud Lighthouse — Lightweight cloud server for Go applications
👉 View current pricing and promotions
👉 Explore all active deals and offers