SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor

How to Self-Host Open Connector for AI Agents

Run Open Connector for AI agents on your VPS with a pinned image, TLS origin, OAuth callbacks, and backups, so agents no go hold SaaS tokens.

Wetin Open Connector dey do for AI agent

Self-hosting Open Connector dey put one auth gateway between your AI agents and every software as a service (SaaS) API wey dem dey call. So, agent no ever hold provider token. Na source-available gateway from OOMOL Lab, and modified Apache 2.0 license with extra conditions dey cover am for now. Releases up to v1.9.4 still remain under Apache 2.0; v1.9.5 introduce the change. If you provide hosted or managed service wey OpenConnector functionality be the main value, you need commercial license from OOMOL. E dey run as one container, keep state for one SQLite file, and expose provider actions over HTTP and MCP (model context protocol).

The wahala start from the second integration. Every provider get im own OAuth (open authorization) flow, im own refresh token lifetime, and im own scope names. If you wire five providers into one agent by hand, you go need five redirect handlers, five credential stores, and five refresh loops wey must run before token expire. Almost nobody dey write that kind code. Instead, dem dey mint one long-lived personal access token for each service and paste am inside agent config, environment file, or the prompt itself. Every tool wey the agent run fit read that token. The token also enter transcript. Na this failure keeping secrets out of AI agents dey describe.

Auth gateway dey split the credential into two parts. Gateway store the provider credential and run the OAuth flow. Agent get runtime token wey only valid against the gateway. When agent call an action, gateway load the stored credential, inject am into the outbound request from the server side, then return only the response body. Agent no ever receive provider access token. So, if agent transcript leak, na one revocable runtime token you lose instead of your GitHub account.

The catalog advertise more than 1,000 providers and 10,000 prebuilt actions. Na the project own figure, and you no fit verify am from outside. Wetin you fit verify na the structure: one HTTP endpoint for each action, one stored connection for each provider, and one token for each agent. If the agent side still new to you, and terms like tool call or MCP server never settle yet, the staged path for how to learn AI agents from scratch go build the loop, the tools, and the safety habits wey gateway like this assume say you already get.

Why you suppose self-host Open Connector instead of using hosted connector service

Hosted connector service dey do the same work, and e dey hold refresh tokens for every provider wey you connect to am. Refresh token for Google or GitHub na long-lived key to your mail and repositories. If dem breach, your own data don breach too. Self-hosting move those records enter SQLite for machine wey you rent and administer, sealed with key wey never comot your box.

Call the cost clearly before you start. This VPS go become the most valuable server wey you run. E hold working credentials for about a dozen services inside one file, so treat am like password manager host: firewall wey expose only 443, no shared logins, backup wey you don restore at least once, and alert when e stop responding. If you no go put your password vault for this box, no put the connector there too.

Install kɔntrol version before you install anything

Open Connector don young. This guide dey use v1.3.3, wey dem publish on 30 July 2026, as the version wey dem pin for installation snapshot of 1 August 2026. E no be the newest release again. The registry publish tip tag too, wey dem build from the newest commit for main.

For project wey still new like this, moving tags dey change often. A docker compose pull wey jump two releases fit change endpoint wey your agent depend on, and you go spend the evening dey debug am as if na agent problem. Pin the image to release tag, then upgrade when you decide, after you don read the release notes.

Deploy Open Connector behind TLS for your own VPS

Before the container start, you need:

  • Docker Engine 28.0.0 or newer with the Compose plugin, for Ubuntu 24.04 or something close to am
  • one hostname wey A record dey point to this VPS, for example connect.example.com
  • one reverse proxy wey don already terminate TLS (transport layer security) for that hostname
  • two random secrets, wey you go generate below

The Traefik reverse proxy for multiple Docker Compose apps guide explain the proxy side. The same certificate setup, from start to finish for one app, dey for the n8n on a VPS with Docker and HTTPS guide.

Generate the secrets first. The encryption key dey protect the credentials wey dem store. The admin token dey protect the web console and the whole /api surface. None of dem get default value, and the runtime fit start normally without dem.

mkdir -p ~/open-connector && cd ~/open-connector
umask 077
printf 'OOMOL_CONNECT_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 32)" > .env
printf 'OOMOL_CONNECT_ADMIN_TOKEN=%s\n' "$(openssl rand -base64 32)" >> .env
chmod 600 .env

Copy both values enter your password manager now, before the first start. The encryption key no get recovery path, and the failure list further down explain why.

Now compose.yaml. E differ from the upstream example for two places, and both dey important.

services:
  connector:
    image: ghcr.io/oomol-lab/open-connector:v1.3.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - connector-data:/app/data
    environment:
      OOMOL_CONNECT_DATA_DIR: /app/data
      OOMOL_CONNECT_ORIGIN: "https://connect.example.com"
      OOMOL_CONNECT_ENCRYPTION_KEY: "${OOMOL_CONNECT_ENCRYPTION_KEY:?set this in .env}"
      OOMOL_CONNECT_ADMIN_TOKEN: "${OOMOL_CONNECT_ADMIN_TOKEN:?set this in .env}"

volumes:
  connector-data:

The first change na the pinned tag instead of latest. The second one na the port. The upstream file publish 3000:3000, wey bind every interface for the host. Docker write published ports into the NAT (network address translation) table before the ufw filter chain see the packet, so ufw deny 3000 no close that port, as the trap for why Docker ports bypass ufw explain. Writing 127.0.0.1:3000:3000 publish only on the loopback interface, and your reverse proxy connect from the same host. Before Docker Engine 28.0.0, hosts for the same L2 segment fit reach ports wey dem publish to localhost. Na why this guide require 28.0.0 or newer.

The :? mark every variable as required, so the stack refuse to start when .env no dey, instead of starting with credentials wey no get encryption. Keeping the values for .env instead of the compose file follow the pattern from Docker Compose env files and secrets. Keep /v1 and /mcp private for the reverse proxy until you don create the first runtime token below.

docker compose up -d
docker compose logs -n 30 connector
curl -s http://127.0.0.1:3000/health
sudo ss -tlnp | grep 3000

/health answer { "ok": true } once the runtime don start. ss must print 127.0.0.1:3000. If one line show 0.0.0.0:3000, e mean say the listener bind to every IPv4 interface, but e no prove say internet fit reach am: upstream firewalls or rules for Docker's DOCKER-USER chain fit still block external traffic. If the health check return connection refused, e mean say the container never start listening, so read the logs before you touch the proxy.

Traefik labels for the same service
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.connector.rule=Host(`connect.example.com`)"
      - "traefik.http.routers.connector.entrypoints=websecure"
      - "traefik.http.routers.connector.tls.certresolver=le"
      - "traefik.http.services.connector.loadbalancer.server.port=3000"

When Traefik dey run inside Docker for the same host, attach this service to the Traefik network and delete the ports: block, because Traefik dey reach the container through the internal network and nothing need publish to the host. certresolver=le must match the resolver name for your Traefik static config, or the router go come up without certificate.

Why OAuth callbacks need the right origin

Na OOMOL_CONNECT_ORIGIN be the setting wey people dey skip, and when dem skip am, OAuth go break for way wey go look like provider bug. The runtime dey build redirect URI from that origin, for form <origin>/oauth/callback. If you leave am unset, the origin go default to http://localhost:3000. Then the runtime go send redirect URI http://localhost:3000/oauth/callback to the provider, while your OAuth app get https://connect.example.com/oauth/callback registered. The two strings no match, so GitHub go answer:

The redirect_uri MUST match the registered callback URL for this application.

OAuth provider dey redirect browser back to that URI, so browser must fit reach the address. OAuth no require public hostname for every case: OpenConnector document GitHub callbacks for http://localhost:3000/oauth/callback. This VPS deployment dey use hostname and certificate so browser fit reach the gateway through its HTTPS origin. Set the origin before the first start, because the value dey read when startup happen. After you edit .env or compose.yaml, run docker compose up -d again to apply am.

Connect your first provider over OAuth

First create the OAuth app for the provider. For GitHub, go Settings, then Developer settings, then OAuth Apps, then New OAuth App. Set the authorization callback URL to https://connect.example.com/oauth/callback. Keep the client ID and the client secret.

Every /api call dey carry the admin token, so export am once for the shell session.

export ADMIN_TOKEN='paste-the-admin-token'
curl -s https://connect.example.com/api/oauth/configs \
  -H "authorization: Bearer $ADMIN_TOKEN"

That listing dey show the redirect URI wey the runtime dey expect for each provider. This na the fastest way to check say your origin don take effect. If e still dey show localhost, the container dey run with the old value, and the OAuth flow go fail for the last step.

Store the client credentials, then start authorization.

curl -s -X PUT https://connect.example.com/api/oauth/configs/github \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"clientId":"...","clientSecret":"..."}'

curl -s -X POST https://connect.example.com/api/oauth/authorizations \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"service":"github"}'

The second call dey return an authorizationUrl. Open am for browser, approve the scopes, and the provider go send the browser back to /oauth/callback. For there, the runtime go exchange the code and store the credential. The web console for your origin dey guide you through the same steps with a form, behind the same admin token. Providers wey use plain API key no need all this: PUT /api/connections/<service> with {"authType":"api_key","values":{"apiKey":"..."}} go store the key directly.

Give each agent one runtime token, never give am credential

Agent go authenticate to gateway with runtime token wey admin API mint.

curl -s -X POST https://connect.example.com/api/runtime-tokens \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"research-agent"}'

Response get token wey start with oct_. Issue one for each agent and name am after that agent, because if you revoke token wey you no fit identify, you go revoke all of dem. Agent go then call actions through ordinary HTTP.

curl -s -X POST https://connect.example.com/v1/actions/github.get_current_user \
  -H "authorization: Bearer oct_..." \
  -H 'content-type: application/json' \
  -d '{"input":{}}'

Healthy response na envelope wey success field be true, while provider payload dey under data. GitHub token no dey anywhere inside that response. For MCP client, point am to https://connect.example.com/mcp with the same bearer header. Gateway go provide discovery tools like search_actions and execute_action instead of one tool for each API. This keep the agent tool list small. Run MCP servers for one VPS explain the client side of this wiring.

Do one more check before you call the work complete. Repeat the action call after you delete the authorization header. The project own quickstart call /v1 without any bearer, so if runtime auth no configure for the install, anybody wey fit reach the port fit execute actions. If your unauthenticated call succeed, you get two options: configure runtime tokens and confirm say the anonymous call now fail, or restrict /api, /v1 and /mcp for the reverse proxy to the addresses wey your agents dey come from. /oauth/callback must dey reachable by the browser wey dey complete authorization; e no need open to the whole internet.

Reduce the action list to wetin agent need

Gateway wey get one thousand providers behind am na broad surface to give language model. E go become broader immediately when model start reading text wey e no write, because page wey your own SearXNG instance return for agent web searches fit carry instructions wey target any action wey agent get access to. The same restraint wey make coding agent take the smallest change wey work suppose apply to permissions: give am only the few actions wey the job really need, and nothing more. Two controls fit narrow am.

OOMOL_CONNECT_ALLOWED_ACTIONS dey take comma-separated allowlist and e understand service.* and *. OOMOL_CONNECT_BLOCKED_ACTIONS na denylist, and denylist get priority. If you set allowlist to github.get_current_user,github.list_issues, every other action go dey refused no matter wetin agent request, and na this one separate mistake from incident. Runtime tokens get their own action rules on top the global ones, and their allowedProxies list dey empty when dem start, so POST /v1/proxy/:service go dey refused until you grant am. That proxy endpoint dey forward raw request go provider with your credential attached, so leave am empty unless one particular agent need am.

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK default na false, wey stop self-hosted provider connection from pointing to private address like cloud metadata service for 169.254.169.254, or your database for the same network. Leave am off. Turn am on only for provider wey you host yourself.

Back up the box wey hold every token

Two things dey important, and each one useless without the other. The database for /app/data/connect.sqlite inside the connector-data volume dey hold the sealed credentials. The encryption key for .env dey unseal dem. Volume backup without the key no go restore anything, and key without the volume no go restore anything. So keep the key for your password manager, and include the volume for your normal backup rotation.

Stop the container while you dey copy the SQLite file, because copy wey happen during a write fit restore as corrupt database.

docker volume ls | grep connector-data
docker compose stop connector
docker run --rm -v open-connector_connector-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/connector-data.tgz -C /data .
docker compose start connector

The volume name na your project directory plus _connector-data. Na why the first command dey there: paste the real name into the third command. Send the archive off the VPS with restic backups from a VPS, wey encrypt am before e leave, because that archive na the credential store.

The runtime dey keep recent action runs as audit records, 5,000 by default, so the console fit show which agent run wetin and when. That log na the first thing to read when agent dey behave strangely. Point an Uptime Kuma status page at https://connect.example.com/health too. When the gateway stop to answer, agents dey fail for confusing ways, and knowing say the gateway dey down fit save one hour of reading agent output.

Wetin fit break, and message wey you go see

redirect_uri_mismatch for provider side. The origin and registered callback URL no be the same. Compare the exact string from /api/oauth/configs with the provider app settings. Check https against http, including any trailing slash.

Every /api call dey return 401. Admin token header no dey, or dem spell am wrong. The header na Authorization: Bearer <token>, and web console dey ask for the same token.

The container dey run, and credentials dey plain text. This one happen when OOMOL_CONNECT_ENCRYPTION_KEY no reach the container, because runtime dey store credential records without encryption instead of refusing to start. Prove am for your own install: connect provider with an API key wey you fit recognise, then search the database for am.

docker compose cp connector:/app/data/connect.sqlite /tmp/connect.sqlite
grep -c 'github_pat_' /tmp/connect.sqlite
shred -u /tmp/connect.sqlite

Count wey pass 0 mean say the key no dey take effect. Check say .env dey the same directory with compose.yaml, and say docker compose config dey show the value. When you set the key, the same search go return 0, because AES-256-GCM seal the record (advanced encryption standard, 256-bit key, Galois/counter mode).

Nothing dey decrypt after restore. Encryption key don change or e lost. By design, the key no dey written beside the data, so no recovery path dey, and support ticket no fit help. Connect every provider again. Rotation dey supported through separate key variable and data command for the runtime, so read the current release notes before you rotate anything.

The agent dey show error for action wey e fit see for catalog. Discovery and execution dey separate. Action fit appear for search_actions and still OOMOL_CONNECT_ALLOWED_ACTIONS fit reject am, denylist fit block am, or the runtime token own rules fit refuse am.

Upgrades. Back up the volume, edit the image tag to the new release, then docker compose pull && docker compose up -d. Monitor docker compose logs -n 50 connector for migration line, then run the health check and one real action again before you trust am. Rollback mean say you put the old tag back. E work only because you pin the tag.

FAQ

I need public domain before I fit self-host Open Connector?

For providers wey dey use API key, no: gateway for 127.0.0.1 don dey enough. OAuth no always require am too: OpenConnector document GitHub local callback as http://localhost:3000/oauth/callback. The browser wey dey complete authorization must fit reach the callback URL. For this VPS deployment, set OOMOL_CONNECT_ORIGIN to your https:// hostname before the first start, and register <origin>/oauth/callback for the provider OAuth app.

Wetin go happen if I lose Open Connector encryption key?

You no go fit decrypt the credentials wey dey stored, and recovery no dey. The key no dey ever stored together with the data deliberately, so anybody wey hold the database no fit read am, including you. Your only option na to set new key and reconnect every provider. Keep the key for password manager and the database for your backup rotation, because restore need both.

My AI agent fit see the provider access token?

E no fit when e call through the gateway. The agent authenticate with runtime token wey start with oct_, and the gateway inject the provider credential into the outbound request for the server, then return only the response. Two things fit break this protection: the /v1/proxy/:service endpoint, wey forward raw requests with your credential attached and wey grants start empty for good reason, and when you paste API key into the agent yourself, wey bypass the gateway completely.

Gateway suppose dey reachable from public internet?

The browser wey dey complete authorization must fit reach the OAuth callback, but the whole internet no need get access. With Docker Engine 28.0.0 or newer and default bridge networking, publish the container port on 127.0.0.1 so the published port go remain accessible only from the host, then put reverse proxy in front. Older Docker releases allow hosts for the same L2 segment reach ports wey localhost publish. Then test one action call without authorization header. If e succeed, restrict /api, /v1 and /mcp for the proxy to the addresses wey your agents dey use until na only authenticated calls dey work.

Open Connector ready for production use?

Current releases dey use modified Apache 2.0 license with extra conditions; releases through v1.9.4, including the pinned v1.3.3, still remain Apache 2.0. Hosted or managed connector service wey OpenConnector functionality be the main value need commercial license from OOMOL under the current license. The v1.3.3 installation commands remain snapshot of 1 August 2026, and that release ship on 30 July 2026. Run am pinned to release tag, never on latest or tip, read the release notes before every upgrade, and keep volume backup wey you don restore once. The design make sense for box wey you own, and the risk na version churn, not the architecture.

Dis article help you?

#ai-agents#oauth#self-hosting#integrations#source-available