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.
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
Most languages have a deployment friction point:
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.
| 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 |
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
You have two options: build on the server, or cross-compile locally and upload the binary.
# 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
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
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))
}
cd ~/apps/mygoapp
go build -o myapp .
# Test it
PORT=8080 ./myapp
# Visit http://YOUR_SERVER_IP:8080
# Ctrl+C to stop
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
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
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
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.
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.
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.
| 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 |
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.
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