πŸš€ Deploy Golang API to VPS with Nginx & HTTPS

This guide documents the complete production deployment process of a Golang API to a VPS.

Prerequisites:

  • A VPS with Ubuntu 22.04 (You can get one from SumoPod)
  • A domain name (You can get one from Namecheap | Cloudflare | etc)
  • A Golang API

We will:

  • Install required packages
  • Configure firewall (UFW)
  • Clone project
  • Configure environment variables
  • Build and run binary
  • Setup systemd service
  • Configure Nginx reverse proxy
  • Point custom domain
  • Enable HTTPS (SSL)
  • Verify auto renewal

πŸ— Final Architecture

Internet
   ↓
HTTPS (443)
   ↓
Nginx Reverse Proxy
   ↓
http://localhost:6969
   ↓
Go Binary (/opt/<appname>/<appname>)
   ↓
systemd Service

Phase 1 β€” Install Requirements

# Connect to your vps
ssh root@YOUR_VPS_IP

# Update package list
sudo apt update && sudo apt upgrade -y

# Install Nginx, Git, Certbot, and Build Tools
sudo apt install -y git uvw curl nginx build-essential
Description:
- git: version control system
- uvw: (Uncomplicated Firewall) tool for managing firewall rules
- curl: tool for transferring data with URLs
- nginx: web server and reverse proxy
- build-essential: collection of packages required for building software from source code

Phase 2 - Create propper user

# Create a new user
sudo adduser vicktor

# Add the user to the sudo group
sudo usermod -aG sudo vicktor

# Switch to the new user
su - vicktor

Phase 3 β€” Configure Firewall (UFW)

# Allow SSH, Nginx Full
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'

Enable UFW

sudo ufw enable

Check status

sudo ufw status

Output:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

Phase 4 β€” Clone & Build Project

Move to /opt:

cd /opt

Clone the repository:

# Clone the repository
git clone <github_repo>/username/repo_name.git

# Navigate to the project directory
cd repo_name

Change ownership of the project directory:

sudo chown -R $USER:$USER /opt/repo_name

Configure environtment variables

cd /opt/repo_name
cp .env.example .env

Build the project

cd /opt/repo_name
go build -buildvcs=false -o <appname>

Verify

If successful, no output will appear.

Check:

ls -l <appname>

Output:

-rwxr-xr-x 1 <user> <user> <size> <date> <appname>

Test Application manually

./<appname>

Output:

<appname> is running on port <port>

Test locally your API health check endpoint:

curl http://localhost:<port>/health

Output:

{
    "status": "ok"
}

If it’s works, press Ctrl + C to stop the application.

Phase 5 β€” Setup systemd service

Create service:

sudo nano /etc/systemd/system/<appname>.service
[Unit]
Description=<appname> API
After=network.target

[Service]
User=root
WorkingDirectory=/opt/<appname>
ExecStart=/opt/<appname>/<appname> http
Restart=always
Environment=APP_ENV=production

[Install]
WantedBy=multi-user.target

Reload systemd:

sudo systemctl daemon-reexec
sudo systemctl daemon-reload

Enable and start the service:

sudo systemctl enable <appname>
sudo systemctl start <appname>

Check status:

sudo systemctl status <appname>

You will see the output like this:

● <appname>.service - <appname> API
     Loaded: loaded (/etc/systemd/system/<appname>.service; enabled; preset: enabled)
     Active: active (running) since <date> <time>; <time> ago
   Main PID: <pid> (<appname>)
      Tasks: 1 (limit: <limit>)
     Memory: <memory>MiB
        CPU: <cpu>ms
      CGroup: /system.slice/<appname>.service
              └─<pid> /opt/<appname>/<appname> http

Veryfy port listening:

ss -tulnp | grep 6969

Output:

tcp   LISTEN 0      4096                  *:6969             *:*    users:(("<appname>",pid=<pid>,fd=<fd>))

Phase 6 β€” Pointing Domain & Configure Nginx Reverse Proxy

Pointing Domain

Create DNS A record:

Type: A
Name: api
Value: <YOUR_VPS_IP>

wait for DNS propagation (2-5 minutes).

verify DNS propagation on your vps:

ping api.<your_domain>

Output:

PING api.<your_domain> (<YOUR_VPS_IP>): 56 data bytes
64 bytes from <YOUR_VPS_IP>: icmp_seq=0 ttl=64 time=0.053 ms
64 bytes from <YOUR_VPS_IP>: icmp_seq=1 ttl=64 time=0.048 ms
...

Configure Nginx Reverse Proxy

sudo nano /etc/nginx/sites-available/<your_domain>

paste:

server {
    listen 80;
    server_name api.<your_domain>;

    location / {
        proxy_pass http://localhost:6969;
        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-For  warded-Proto $scheme;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/<your_domain> /etc/nginx/sites-enabled/

Test Nginx configuration:

sudo nginx -t

Output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload Nginx:

sudo systemctl reload nginx

Verify:

curl -I http://api.<your_domain>

Output:

HTTP/1.1 200 OK
Server: nginx
Date: <date>
Content-Type: application/json
Content-Length: 13
Connection: keep-alive

Phase 7 β€” Enable HTTPS (Let’s Encrypt)

Install certbot:

sudo apt install certbot python3-certbot-nginx


// Description
- certbot: tool for obtaining and renewing SSL certificates
- python3-certbot-nginx: Nginx plugin for certbot

Rrequest Certificate:

sudo certbot --nginx -d api.<your_domain>

Output:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator Nginx, Installer Nginx
Requesting new certificate
Performing the following challenges:
http-01 challenge for api.<your_domain>
Waiting for verification...
Cleaning up challenges
...

Verify:

curl -I https://api.<your_domain>

Output:

HTTP/2 200 OK
server: nginx
date: <date>
content-type: application/json
content-length: 13
last-modified: <date>
etag: "<etag>"
strict-transport-security: max-age=31536000; includeSubDomains

Auto Renew Certificate

sudo certbot renew --dry-run

Final Result

Your production API is live at:

https://api.<your_domain>

Check your Health Endpdoint:

curl https://api.<your_domain>/health

Output:

{
    "status": "ok"
}

πŸŽ‰ Conclusion

You now have:

  • Golang API running on VPS
  • Managed by systemd
  • Reverse proxied via Nginx
  • Custom domain configured
  • HTTPS enabled
  • Auto SSL renewal
  • Production-ready deployment

πŸŽ‰ Next Steps

  • Configure database
  • Add monitoring and logging
  • Implement rate limiting
  • Add authentication and authorization
  • Add caching
  • Add rate limiting
  • Add rate limiting