Securing API Access Using API Gateways on VPS

Updated on Apr 16, 2026
Kimberly N
12 MINS READ
Table of Contents
VPS API Gateway Security Guide

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:

Bash
sudo apt update && sudo apt upgrade -y

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

Bash
sudo apt install ufw -ysudo ufw default deny incomingsudo ufw default allow outgoingsudo ufw allow OpenSSHsudo ufw allow 80/tcpsudo ufw allow 443/tcp

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

Bash
sudo ufw enablesudo 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:

Bash
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 updatesudo 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:

Bash
sudo usermod -aG docker $USER

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

Bash
sudo mkdir -p ~/api-gw/kongcd ~/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:

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

Add the following configuration to the file:

YAML
_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 serviceplugins:  # 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:

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

Add the following configuration to the file:

YAML
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:

Bash
cd ~/api-gwdocker compose up -d

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

Bash
docker compose ps

Run a quick local check with HTTP:

Bash
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:

Bash
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 updatesudo apt install caddy -y

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

Bash
sudo nano /etc/caddy/Caddyfile

Edit the config file with:

Bash
# Replace with your real domainapi.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 you@example.com}

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

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

Now, you can test HTTPS with the command below:

Bash
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:

Bash
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:

Bash
sudo apt install python3-pip -ypip3 install --user pyjwt

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

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

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

Bash
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:

Bash
mkdir -p ~/mtls && cd ~/mtls # Private CA (10 years)openssl genrsa -out myCA.key 4096openssl 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 2048openssl 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:

Bash
sudo cp ~/mtls/myCA.pem /etc/ssl/myca.pemsudo 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:

Bash
sudo nano /etc/caddy/Caddyfile

Edit the file as shown below:

Bash
api.example.com {    encode gzip    reverse_proxy 127.0.0.1:8000     tls you@example.com {        client_auth {            mode require_and_verify            trusted_ca_cert_file /etc/ssl/myca.pem        }    }}

Reload Caddy to apply the changes:

Bash
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:

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

With client cert and key:

Bash
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).

Bash
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:

Bash
# On your laptopssh -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:

Bash
# Follow logsdocker compose logs -f kongdocker compose logs -f httpbin # Health check Kong proxycurl -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.

Bash
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.

Bash
docker pull kong:latestdocker compose up -ddocker 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.

YAML
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:
Bash
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.

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.

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.

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.

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.