Journal11 min read

How I Stopped Burning OpenAI Credits: Self-Hosting CLIProxyAPI on an Ubuntu VPS

A production deployment guide for CLIProxyAPI v6.8.52 on Ubuntu 24.04 with systemd, Git-backed token storage, Nginx, Let's Encrypt SSL, and OpenCode/Codex/Claude integrations.

March 14, 202611 min read2049 words
How I Stopped Burning OpenAI Credits: Self-Hosting CLIProxyAPI on an Ubuntu VPS
Reading
11 min read
Word Count
2049
Published
March 14, 2026
CLIProxyAPICLIProxyAPI
UbuntuUbuntu
NginxNginx
SSL/TLSSSL/TLS
OpenCodeOpenCode
CodexCodex

How I Stopped Burning OpenAI Credits: Self-Hosting CLIProxyAPI on an Ubuntu VPS

I hit a very specific developer pain point this year: I was running out of OpenAI credits while my non-technical friends barely touched theirs.

Same family plan. Same billing cycle. Totally different burn rates.

I was doing real engineering work every day (OpenCode sessions, Codex CLI loops, Claude Code debugging), and my usage curve looked like a denial-of-service attack against my own wallet.

So instead of playing subscription roulette, I set up a single self-hosted AI gateway on a VPS with CLIProxyAPI v6.8.52.

Result: one endpoint and one auth layer for OpenAI-compatible clients, shared across tools and providers.

Stack in this guide: Ubuntu 24.04 LTS, CLIProxyAPI v6.8.52, systemd, Git-backed token storage, Nginx reverse proxy, Let's Encrypt SSL, OpenCode/Codex CLI/Claude Code integration.


1) Overview

CLIProxyAPI is an open-source AI proxy that unifies multiple providers (OpenAI/Codex, Gemini, Claude, and others) behind one authenticated API gateway.

It exposes OpenAI-compatible endpoints (/v1/*) and provider-specific compatibility bridges, so coding agents and CLIs can connect without provider-specific rewrites.

This guide covers a full production deployment on Ubuntu VPS:

  • Binary installation
  • systemd service hardening
  • Git-backed token storage
  • Nginx reverse proxy
  • HTTPS via Let's Encrypt
  • OpenCode, Claude Code, and Codex CLI wiring

1.1 Architecture Overview

| Property | Value / Description | |---|---| | Binary | /home/<user>/cliproxyapi/cli-proxy-api | | Version | v6.8.52 | | Service manager | systemd | | Internal port | 8317 | | Public access | https://cli.yourdomain.com | | Web UI | https://cli.yourdomain.com/management.html | | Token storage | Private Git repo (auto-commit & push on login) | | Reverse proxy | Nginx | | SSL | Let's Encrypt (Certbot) | | OS | Ubuntu 24.04 LTS |

System architecture diagram: AI clients to Nginx to CLIProxyAPI with gitstore and external providers
Traffic, auth, and token-sync flow in a single view.

1.2 Model Catalog (Example from /v1/models)

After provider login succeeds, your OpenAI-compatible endpoint exposes model IDs based on your authenticated providers and account entitlements.

The list below is illustrative (not guaranteed on every deployment):

  • gpt-5, gpt-5.1, gpt-5.1-codex, gpt-5.1-codex-max, gpt-5.3-codex, gpt-5.4
  • gemini-2.5-flash, gemini-2.5-flash-lite, gemini-3-pro-high, gemini-3.1-flash-image
  • claude-sonnet-4-6, claude-opus-4-6-thinking
  • gpt-oss-120b-medium

Query your live catalog any time:

curl https://cli.yourdomain.com/v1/models \
  -H 'Authorization: Bearer your-proxy-api-key'

2) Prerequisites

2.1 VPS and OS Requirements

  • Ubuntu 22.04+ (this guide uses 24.04 LTS)
  • Non-root user with sudo
  • Open ports 80 and 443
  • Keep 8317 private (localhost-only) unless you have a specific reason to expose it
  • Domain with DNS control
  • Git installed
sudo apt update
sudo apt install git -y

2.2 Private GitHub Repo for Token Storage

Git storage allows CLIProxyAPI to:

  1. Clone your private repo at startup
  2. Persist config/config.yaml
  3. Commit and push OAuth token files after successful Web UI login

Create:

  • A private repo (e.g. my-cliproxyapi-config)
  • At least one initial commit (README.md is enough)
  • A PAT with repo scope

In my setup, classic PAT was the stable path. If fine-grained tokens produce 403 in your org policy setup, switch to a classic token scoped for repo access.


3) Binary Installation

3.1 One-Click Linux Installer

curl -fsSL https://raw.githubusercontent.com/brokechubb/cliproxyapi-installer/refs/heads/master/cliproxyapi-installer | bash

Installer behavior note: it drops the binary in the current working directory.

3.2 Locate the Binary

find / -name 'cli-proxy-api' 2>/dev/null

Use the root-level binary path (not a version-subfolder copy) for ExecStart.

3.3 Prepare Working Directory

mkdir -p ~/cliproxyapi
cd ~/cliproxyapi

curl -fsSL https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/main/config.example.yaml \
  -o config.example.yaml

4) Application Configuration

When Git storage is active, the operational config lives at:

~/cliproxyapi/gitstore/config/config.yaml

Minimal baseline:

host: "127.0.0.1"
port: 8317

remote-management:
  allow-remote: false
  secret-key: "your-management-key"
  disable-control-panel: false

auth-dir: "/home/<your-linux-user>/cliproxyapi/gitstore/auths"

api-keys:
  - "your-proxy-api-key"

debug: false

Important separation:

  • api-keys: credentials your downstream clients send
  • OAuth token files: provider credentials generated via Web UI login

4.1 Key Fields Reference

| Field | Default | Description | |---|---|---| | host | "" | Upstream default is all interfaces; for production, prefer 127.0.0.1 behind Nginx | | port | 8317 | Listen port | | remote-management.allow-remote | false | Keep false unless you intentionally publish management access | | remote-management.secret-key | empty | Management secret (empty can disable management) | | auth-dir | ~/.cli-proxy-api | OAuth token directory; in GitStore setups, align this with your gitstore auth path | | api-keys | [] | Accepted client API keys | | request-retry | 3 | Retry budget for transient upstream errors | | routing.strategy | round-robin | Credential selection strategy |


5) systemd Service (Production)

Create a root-only environment file first:

sudo mkdir -p /etc/cliproxyapi
sudo chmod 700 /etc/cliproxyapi
sudo tee /etc/cliproxyapi/cliproxyapi.env >/dev/null <<'EOF'
GITSTORE_GIT_URL=https://github.com/YOUR_USER/YOUR_REPO.git
GITSTORE_GIT_USERNAME=YOUR_GITHUB_USERNAME
GITSTORE_GIT_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
GITSTORE_LOCAL_PATH=/home/<your-linux-user>/cliproxyapi
MANAGEMENT_PASSWORD=your-management-password
EOF
sudo chmod 600 /etc/cliproxyapi/cliproxyapi.env

Then create /etc/systemd/system/cliproxyapi.service:

[Unit]
Description=CLIProxyAPI Service
After=network.target

[Service]
Type=simple
User=<your-linux-user>
WorkingDirectory=/home/<your-linux-user>/cliproxyapi
EnvironmentFile=/etc/cliproxyapi/cliproxyapi.env

# Minimal hardening defaults
NoNewPrivileges=true
PrivateTmp=true
UMask=0077

ExecStart=/home/<your-linux-user>/cliproxyapi/cli-proxy-api
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

5.1 Service Lifecycle Commands

sudo systemctl daemon-reload
sudo systemctl enable cliproxyapi
sudo systemctl start cliproxyapi

sudo systemctl status cliproxyapi
sudo journalctl -u cliproxyapi -n 50 --no-pager
sudo journalctl -u cliproxyapi -f

Critical gotcha:

GITSTORE_LOCAL_PATH must point to the parent directory, not .../gitstore.

Version note: GitStore env var names/behavior can evolve by release. Validate on your target version using service logs after first boot.


6) Git-Backed Token Storage Internals

6.1 Boot + Sync Sequence

At startup:

  1. Clone remote repo into GITSTORE_LOCAL_PATH/gitstore/
  2. Load config/config.yaml
  3. Bootstrap config from example if missing
  4. Commit + push token file updates after OAuth logins

Typical repo structure:

your-repo/
├── config/
│   └── config.yaml
└── auths/
    └── codex-user@gmail.com-plus.json

6.2 Verify Push Path

cd ~/cliproxyapi/gitstore
git remote -v
git log --oneline

sudo systemctl show cliproxyapi --property=Environment | sed 's/GITSTORE_GIT_TOKEN=[^ ]*/GITSTORE_GIT_TOKEN=***REDACTED***/g'

If push auth fails:

  • Recheck token scope/expiry
  • Confirm repo write access
  • Validate URL and username format

7) Web UI + OAuth Provider Login

7.1 Access

  • Local: http://localhost:8317/management.html
  • Public (recommended with restriction): https://cli.yourdomain.com/management.html

For production, avoid exposing management UI globally. Restrict by source IP or keep it localhost-only via SSH tunnel.

7.2 OAuth Flow (Codex / Gemini / Claude)

  1. Open provider page in management UI
  2. Click Login
  3. Complete provider auth in browser
  4. Copy full callback URL (e.g. http://localhost:1455/auth/callback?...)
  5. Paste callback URL back in UI and submit

On success, token JSON is persisted and pushed to your Git repo.

OAuth callback and git commit flow diagram for CLIProxyAPI
Auth flow turns browser callback events into versioned token state in Git.

8) Nginx Reverse Proxy + Let's Encrypt SSL

8.1 DNS A Record

Point cli.yourdomain.com to your VPS public IP.

8.2 Install Nginx

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

8.3 Nginx Site Config

/etc/nginx/sites-available/cli.yourdomain.com

server {
    listen 80;
    server_name cli.yourdomain.com;

    client_max_body_size 10m;

    location /v1/ {
        proxy_pass         http://127.0.0.1:8317;
        proxy_http_version 1.1;
        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_buffering    off;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_buffer_size 16k;
        proxy_buffers 8 16k;
    }

    location = /management.html {
        allow <your-static-ip>;
        deny all;
        proxy_pass         http://127.0.0.1:8317;
        proxy_http_version 1.1;
        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;
    }

    location / {
        proxy_pass         http://127.0.0.1:8317;
        proxy_http_version 1.1;
        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_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

Enable + test:

sudo ln -s /etc/nginx/sites-available/cli.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

8.4 SSL with Certbot

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d cli.yourdomain.com
sudo nginx -t
sudo systemctl status certbot.timer

8.5 Firewall

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

9) OpenCode Integration

OpenCode custom providers are configured in project-root opencode.json.

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "cliproxy": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "CLIProxy",
      "options": {
        "baseURL": "https://cli.yourdomain.com/v1",
        "apiKey": "your-proxy-api-key"
      },
      "models": {
        "gpt-5.1-codex": { "name": "GPT-5.1 Codex" },
        "gpt-5.1-codex-max": { "name": "GPT-5.1 Codex Max" },
        "gpt-5.3-codex": { "name": "GPT-5.3 Codex" },
        "gemini-2.5-flash": { "name": "Gemini 2.5 Flash" },
        "claude-opus-4-6-thinking": { "name": "Claude Opus 4.6 Thinking" }
      }
    }
  },
  "model": "cliproxy/gpt-5.1-codex"
}

Critical details:

  • In the OpenCode version used for this setup, key is provider (singular). If you see a schema error, verify against your installed OpenCode schema and adjust.
  • Custom providers may not show in /connect UI (expected)
  • model field still works directly

10) Codex CLI + Claude Code + Generic Clients

Claude Code

export ANTHROPIC_BASE_URL=https://cli.yourdomain.com
export ANTHROPIC_API_KEY=your-proxy-api-key
claude

If your local Claude Code build ignores ANTHROPIC_BASE_URL, check the CLI docs/help for the supported base URL override variable in your version.

Codex CLI

export OPENAI_BASE_URL=https://cli.yourdomain.com/v1
export OPENAI_API_KEY=your-proxy-api-key
codex

If your codex build ignores OPENAI_BASE_URL, confirm the exact override flag/env var with codex --help.

Generic OpenAI-Compatible Call

curl https://cli.yourdomain.com/v1/chat/completions \
  -H 'Authorization: Bearer your-proxy-api-key' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-5.1-codex",
    "messages": [{"role": "user", "content": "Write binary search in Python"}]
  }'

11) Troubleshooting Matrix (Real Failure Modes)

| Error / Symptom | Likely Root Cause | Fast Fix | |---|---|---| | status=203/EXEC in systemd | Wrong ExecStart path | find / -name cli-proxy-api and fix service file | | destination path already exists | GITSTORE_LOCAL_PATH incorrectly includes /gitstore | Set parent path only; stop service before deleting clone | | git push: authentication required | Invalid/expired token | Regenerate PAT and verify env injection | | 403 Write access not granted | Repo token/scope mismatch | Adjust token type/scope according to org policy | | OpenCode providers is not allowed | Wrong JSON key | Use provider (singular) | | Custom provider missing in /connect | Expected OpenCode behavior | Set model directly in config | | PowerShell curl header issues | Alias mismatch (curl != curl.exe) | Use curl.exe or Invoke-RestMethod | | Web UI 404 (management.html) | Control panel assets not available yet | Check startup logs and control panel flags |

11.1 Diagnostics Pack

sudo journalctl -u cliproxyapi -n 100 --no-pager
sudo journalctl -u cliproxyapi -f

sudo systemctl show cliproxyapi --property=Environment | sed 's/GITSTORE_GIT_TOKEN=[^ ]*/GITSTORE_GIT_TOKEN=***REDACTED***/g'

cd ~/cliproxyapi/gitstore && git log --oneline && git remote -v

sudo nginx -t
sudo ss -tlnp | grep -E '8317|80|443'

curl https://cli.yourdomain.com/v1/models -H 'Authorization: Bearer your-proxy-api-key'
Technical deployment checklist for systemd, GitStore, Nginx, SSL, and runtime API probes
Pre-flight production checklist before routing your daily coding workload to the proxy.
Technical sequence diagram of CLIProxyAPI request lifecycle from client to provider and back
Request lifecycle reference for debugging routing and response behavior under load.

12) Quick Reference Card

| Property | Value | |---|---| | Binary path | ~/cliproxyapi/cli-proxy-api | | Service file | /etc/systemd/system/cliproxyapi.service | | App config | ~/cliproxyapi/gitstore/config/config.yaml | | Auth token dir | ~/cliproxyapi/gitstore/auths/ | | Nginx site | /etc/nginx/sites-available/cli.yourdomain.com | | SSL cert path | /etc/letsencrypt/live/cli.yourdomain.com/ | | Internal port | 8317 | | Public base URL | https://cli.yourdomain.com | | Web UI | https://cli.yourdomain.com/management.html | | Models endpoint | https://cli.yourdomain.com/v1/models | | Chat endpoint | https://cli.yourdomain.com/v1/chat/completions | | OpenCode config key | provider | | OpenCode adapter | @ai-sdk/openai-compatible | | Recommended default model | cliproxy/gpt-5.1-codex |


Final Thoughts

Self-hosting this stack was less about “saving money” and more about controlling the interface between tools, teams, and model vendors.

Once I moved to a central proxy, I stopped juggling provider-specific edge cases across every CLI and started treating AI access like any other production service: versioned, observable, and repeatable.

And yes—my monthly credit panic dropped immediately.

The big win wasn’t just fewer billing surprises. It was having a stable platform where my heavy engineering usage and my friends’ occasional prompting could coexist without chaos.

If you want broader provider coverage or extended routing features, check CLIProxyAPIPlus:

  • https://github.com/router-for-me/CLIProxyAPIPlus

I’m also building a CLI around my own workflow constraints and multiplexed usage patterns:

  • https://github.com/XQuestCode/SubMux