Securing API Access Using API Gateways on VPS

VPS API Gateway Security Guide

Securing API Access Using API Gateways on VPS

VPS API Gateway Security involves using an API gateway like Kong, NGINX, or Traefik on a VPS to protect and manage access to your APIs, which acts as a secure entry point that controls all incoming and outgoing API traffic.

In this guide, you will learn to deploy an open-source API access using Kong DB-less mode with strong controls, including TLS, API keys, JWT, rate-limiting, CORS, and IP allowlists. Proceed to the guide steps below to secure API access using API gateways on a VPS.

We’ll use a VPS hosted on Perlod Hosting to display how to secure API access using an API Gateway.

Requirements For VPS API Gateway Security

We assumed you have a fresh Ubuntu 22.04 or Ubuntu 24.04 VPS with a public IP address. You need a domain name that is pointed to your server’s IP address, for example, api.example.com.

Also, you need SSH access to your server as a non-root user with sudo privileges.

Run the system update and upgrade with the commands below:

sudo apt update && sudo apt upgrade -y 

Install UFW firewall and allow the necessary ports through the firewall with the commands below:

sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Enable your firewall and check its status with the commands below:

sudo ufw enable
sudo ufw status verbose

Note: If you are using a non-standard SSH port, allow it through the firewall.

For further hardening of server access, you can check this full guide on implementing MFA and RBAC.

Install Docker and Docker Compose on your Ubuntu VPS by using the commands below:

sudo apt install ca-certificates curl gnupg lsb-release -y

sudo install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
 | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

Add your non-root user to the Docker group and then log out and log back in to apply the changes:

sudo usermod -aG docker $USER

Also, you must create the project folders and switch to it with the commands below:

sudo mkdir -p ~/api-gw/kong
cd ~/api-gw

We will create a Kong config in ~/api-gw/kong/, and a Compose stack in ~/api-gw/.

Once you are done with these requirements, proceed to the steps below to complete the VPS API Gateway Security with Kong.

Implementing API Security and Routing with a Kong Declarative Config

In this step, you can create a declarative configuration file for Kong, which defines a complete API gateway setup in code.

Create the declarative Kong config file with the command below:

sudo nano ~/api-gw/kong/kong.yml

Add the following configuration to the file:

_format_version: "3.0"
_transform: true

services:
  - name: httpbin
    url: http://httpbin:80
    routes:
      - name: httpbin-route
        paths: ["/httpbin"]
        strip_path: true

# --- Security Plugins bound to the httpbin service
plugins:
  # API Key Auth (header: x-api-key)
  - name: key-auth
    service: httpbin
    config:
      key_names: ["x-api-key"]
      key_in_header: true
      key_in_query: false
      key_in_body: false

  # JWT validation (HS256; `iss` (key id) must match consumer's jwt secret key)
  - name: jwt
    service: httpbin
    config:
      key_claim_name: iss
      secret_is_base64: false
      claims_to_verify:
        - exp

  # Reasonable rate limiting (adjust for your load)
  - name: rate-limiting
    service: httpbin
    config:
      minute: 60
      policy: local
      fault_tolerant: true
      hide_client_headers: false

  # Limit body size to mitigate abuse
  - name: request-size-limiting
    service: httpbin
    config:
      allowed_payload_size: 1
      require_content_length: false

  # IP allowlist (use exact ranges; placeholder open to all—tighten this!)
  - name: ip-restriction
    service: httpbin
    config:
      whitelist:
        - 0.0.0.0/0

  # CORS (permit your frontend origins only)
  - name: cors
    service: httpbin
    config:
      origins:
        - "https://example.com"
      methods: ["GET", "POST", "OPTIONS"]
      headers: ["Authorization", "Content-Type", "x-api-key"]
      exposed_headers: ["X-RateLimit-Limit-Minute", "X-RateLimit-Remaining-Minute"]
      credentials: false
      max_age: 3600

consumers:
  # A demo API consumer with both API key and JWT credentials
  - username: demo
    # API key
    keyauth_credentials:
      - key: supersecretkey123
    # JWT creds (HS256)
    jwt_secrets:
      - key: demo-key
        secret: demo-secret

The configuration establishes a service (httpbin) and a route to it, then layers on a robust suite of security plugins to protect the endpoint.

Notes:

  • Key-Auth: The client must include this in the request header: x-api-key: supersecretkey123.
  • JWT: The client must send a valid token (HS256) with iss = demo-key, signed using demo-secret.
  • IP Allowlist: Only allow real and trusted IP address ranges.
  • CORS: Set the allowed origin to your actual frontend website.

Set up Docker Compose for Kong API Gateway and Demo Backend

At this point, you can create the Docker Compose file, which defines both services, Kong as the gateway and httpbin as the sample API backend, with their networking, dependencies, and configuration. This setup allows you to start the complete gateway ecosystem with all its components using a single command, which ensures consistent deployment across different environments.

Create the Docker Compose stack with the command below:

sudo nano ~/api-gw/docker-compose.yml

Add the following configuration to the file:

version: "3.9"

services:
  httpbin:
    image: kennethreitz/httpbin
    container_name: httpbin
    restart: unless-stopped

  kong:
    image: kong:latest
    container_name: kong
    depends_on:
      - httpbin
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
      KONG_PROXY_LISTEN: "0.0.0.0:8000, 0.0.0.0:8443 ssl"
      # lock down admin API to localhost only
      KONG_ADMIN_LISTEN: "127.0.0.1:8001, 127.0.0.1:8444 ssl"
      KONG_LOG_LEVEL: notice
    volumes:
      - ./kong:/kong/declarative:ro
    ports:
      - "8000:8000"
      - "8443:8443"
      - "127.0.0.1:8001:8001"
      - "127.0.0.1:8444:8444"
    restart: unless-stopped

Run the Composer stack with the command below:

cd ~/api-gw
docker compose up -d

Check for its status that is up and running with the command below:

docker compose ps

Run a quick local check with HTTP:

curl -i http://127.0.0.1:8000/httpbin/get

You must receive a 401 Unauthorized error due to the key and JWT requirements.

Enable Automatic HTTPS with Caddy for API Gateway

To securely display the API gateway to the internet, you need HTTPS. You can use Caddy, which automatically obtains and renews free TLS certificates from Let’s Encrypt. Caddy acts as a reverse proxy, handles all SSL/TLS termination, and passes the decrypted traffic to Kong on its local port.

Use the following commands to install Caddy:

sudo apt install debian-keyring debian-archive-keyring apt-transport-https -y

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
 | sudo tee /usr/share/keyrings/caddy-stable-archive-keyring.gpg >/dev/null

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
 | sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null

sudo apt update
sudo apt install caddy -y

Open and edit the Caddy configuration file with the following command:

sudo nano /etc/caddy/Caddyfile

Edit the config file with:

# Replace with your real domain
api.example.com {
    encode gzip
    # Forward everything to Kong's HTTP port
    reverse_proxy 127.0.0.1:8000

    # Automatic Let's Encrypt. Use a valid email so you get expiry notices.
    tls yo*@*****le.com
}

Enable Caddy and check for its status with the commands below:

sudo systemctl enable caddy
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager

Now, you can test HTTPS with the command below:

curl -i https://api.example.com/httpbin/get

You will see an HTTP/2 401 response from Kong because you don’t have credentials yet.

Test API Key Authentication

At this point, you can verify that API key security is working by requesting the protected endpoint, but this time we’ll include the valid API key in the header. To do this, run the command below:

curl -i https://api.example.com/httpbin/get \
  -H 'x-api-key: supersecretkey123'

This should successfully authenticate you and return a 200 OK response from the backend service.

Note: If you hit the rate limit, you’ll see 429 and rate limit headers.

Test JWT Token Authentication

Next, you can test the JWT authentication layer. This requires generating a properly signed JSON Web Token with the correct issuer and expiration time, then using it in an Authorization header. You can use Python and the PyJWT library to create a valid token that Kong will accept for authentication.

Install Python and PyJWT with the commands below:

sudo apt install python3-pip -y
pip3 install --user pyjwt

Generate HS256 JWT token with iss=demo-key and a 5-minute expiry with the command below:

python3 - <<'PY'
import time, jwt
payload = {"iss":"demo-key","exp":int(time.time())+300}
token = jwt.encode(payload, "demo-secret", algorithm="HS256")
# PyJWT>=2 returns str; earlier returned bytes
print(token if isinstance(token, str) else token.decode())
PY

Copy the printed token and call the gateway with the following commands:

JWT_TOKEN="paste_the_token_here"

curl -i https://api.example.com/httpbin/get \
  -H "Authorization: Bearer $JWT_TOKEN"

You must get a 200 OK response.

Implement Mutual TLS (mTLS) for Enhanced Security

For extra security, you can implement mutual TLS (mTLS), which requires clients to present a valid certificate in addition to traditional authentication. This creates a powerful two-factor security model where both the server and client must verify each other’s identity.

We want to set up a private Certificate Authority, issue client certificates, and configure Caddy to enforce mTLS validation at the edge.

Generate Certificate Authority and Client Certificates

First, you must create your own Certificate Authority to issue and verify client certificates. You need to generate a private CA keypair, then use it to sign individual client certificates.

The client certificate will be what API consumers present to prove their identity during the TLS handshake.

To do this, run the following commands:

mkdir -p ~/mtls && cd ~/mtls

# Private CA (10 years)
openssl genrsa -out myCA.key 4096
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 3650 \
  -out myCA.pem -subj "/CN=MyLocalCA"

# Client cert (825 days)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=api-client"
openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial \
  -out client.crt -days 825 -sha256

Then, copy the myCA.pem to /etc/ssl/myca.pem file with the command below:

sudo cp ~/mtls/myCA.pem /etc/ssl/myca.pem
sudo chmod 0644 /etc/ssl/myca.pem

Configure Caddy to Enforce Client Certificate Verification

Now you must update the Caddy configuration to require and validate client certificates against your CA. Open the Caddy config file with the command below:

sudo nano /etc/caddy/Caddyfile

Edit the file as shown below:

api.example.com {
    encode gzip
    reverse_proxy 127.0.0.1:8000

    tls yo*@*****le.com {
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /etc/ssl/myca.pem
        }
    }
}

Reload Caddy to apply the changes:

sudo systemctl reload caddy

Verify Mutual TLS Enforcement

At this point, test your mTLS setup by making requests both with and without client certificates.

Without client certificates:

curl -i https://api.example.com/httpbin/get
# Expect 400/401/403 (handshake failure or unauthorized)

With client cert and key:

curl -i https://api.example.com/httpbin/get \
  --cert ~/mtls/client.crt --key ~/mtls/client.key \
  -H 'x-api-key: supersecretkey123'
# Expect 200 OK

You can also use JWT instead of the API key.

Secure the Kong Admin API from External Access

The Kong Admin API is a powerful management interface that should never be displayed to the public internet. You already configured it to only listen on localhost (127.0.0.1).

KONG_ADMIN_LISTEN: "127.0.0.1:8001, 127.0.0.1:8444 ssl"

In this step, you will learn how to securely access the Admin API when needed using SSH tunneling, which ensures administrative functions remain protected from unauthorized access:

# On your laptop
ssh -N -L 8001:127.0.0.1:8001 ubuntu@your-vps
# Then call: http://127.0.0.1:8001/

Remember to never show the Admin API publicly.

Operational Best Practices and Maintenance for API Gateway

Running a production API gateway requires ongoing maintenance and monitoring. With these best practices, we want to discuss, you will ensure your gateway remains secure, reliable, and high-performance.

Monitor gateway health and logs with the commands below:

# Follow logs
docker compose logs -f kong
docker compose logs -f httpbin

# Health check Kong proxy
curl -sS http://127.0.0.1:8000 | head -n1

Update Kong configuration safely: When you need to change Kong’s security rules or routing, you’ll modify the kong.yml file and restart the service.

docker compose restart kong

Backup critical configuration files: Regularly back up your Kong declarative config and Caddyfile to version control.

  • Keep ~/api-gw/kong/kong.yml in Git.
  • Backup /etc/caddy/Caddyfile and /var/lib/caddy/.

Safely upgrade Kong and dependencies: Keeping your gateway components updated is essential for security and feature access.

docker pull kong:latest
docker compose up -d
docker compose ps

Implement strict IP access controls: Replace the permissive IP allowlist with specific, trusted IP ranges from your office, data center, or VPN. This security measure ensures only authorized networks can access your APIs.

plugins:
  - name: ip-restriction
    service: httpbin
    config:
      whitelist:
        - 203.0.113.0/24
        - 2001:db8:abcd::/48

Configure CORS for production security: Proper CORS configuration prevents unauthorized websites from making requests to your API. Always specify exact frontend domain names rather than using wildcards, and regularly review these settings as your application evolves to maintain web security boundaries.

Additional Tips:

Troubleshoot Common Issues in API Gateway Security

Even with proper setup, you may encounter issues from authentication failures to rate limiting. Here are some quick solutions for the most common problems you may face:

1. Let’s Encrypt fails in Caddy:

  • Ensure your domain name points to your VPS public IP.
  • Configure firewall rules for ports 80 and 443.
  • Check for Caddy logs with:
sudo journalctl -u caddy -b --no-pager

2. 401 Unauthorized:

  • You may miss or incorrectly x-api-key or Authorization: Bearer ….
  • For JWT: check exp not expired, iss matches demo-key, and secret demo-secret.

3. 429 Too Many Requests: Your rate limit hit; adjust the minute or policy in kong.yml.

4. mTLS handshake errors:

  • Client cert not signed by the CA you installed in Caddy.
  • client_auth mode set to require_and_verify, and ensure correct trusted_ca_cert_file.

That’s it, you are done with VPS API Gateway Security using Kong DB-less mode.

FAQs

What is an API Gateway and Why We Should Use It?

An API Gateway is a server that sits between your users and backend APIs. It protects your backend from attacks, controls who can access your APIs, and helps monitor and manage traffic easily.

What is Kong used for?

Kong is a popular open-source API gateway. It’s lightweight, fast, and comes with built-in plugins for authentication, rate-limiting, CORS, and more.

What’s the difference between API key and JWT authentication?

The API key is a static key that grants access. JWT is a signed token that can expire and include user info. It’s more secure for modern apps.

Conclusion

Securing your API with an API gateway is essential to protect sensitive data, prevent abuse, and control traffic. At this point, you have learned to deploy a secure Kong API gateway, add HTTPS automatically with Caddy, set up strong authentication with API keys and JWT, add rate limits, CORS, and optional mTLS.

This setup ensures that only authorized clients can reach your backend APIs, while your infrastructure stays safe and easy to manage.

Perlod Hosting provides reliable and high-performance VPS environments, which makes it ideal for deploying production-grade API infrastructure.

We hope you enjoy this guide on VPS API Gateway Security. Subscribe to our X and Facebook channels to get the latest security articles and tips.

Post Your Comment

PerLod delivers high-performance hosting with real-time support and unmatched reliability.

Contact us

Payment methods

payment gateway
Perlod Logo
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.