SSD Nodes Learn 8GB RAM — $66/yr
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-02

How to Self-Host Open Connector for AI Agents

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

Wetin Open Connector dey do for AI agent

When you self-host Open Connector, e puts one auth gateway between your AI agents and every software as a service (SaaS) API wey dem dey call. So the agent no go ever hold provider token. OOMOL Lab dey provide am as open source gateway, under Apache 2.0 license. E dey run as one container, e dey keep state for one SQLite file, and e dey expose provider actions through HTTP and MCP (model context protocol).

The wahala dey 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 connect five providers to 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 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, and e go enter the transcript too. Na this failure how to keep secrets comot from AI agents dey describe.

Auth gateway dey split the credential into two parts. The gateway stores the provider credential and runs the OAuth flow. The agent gets runtime token wey valid only against the gateway. When the agent calls an action, the gateway loads the stored credential, injects am into the outbound request for server side, and returns only the response body. The agent no ever receive the provider access token. So if agent transcript leak, you lose one revocable runtime token instead of your GitHub account.

The catalog dey advertise more than 1,000 providers and 10,000 prebuilt actions. Na the project own figure be this, 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.

Why make you 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 cryptographic key to your mail and repositories, and e usually dey remain valid even after password change. If dem breach the service, your own data don breach too. When you host am yourself, those records dey move into SQLite for machine wey you rent and administer, and key wey no dey leave your box dey protect dem.

Talk the cost clearly before you start. This VPS go become the most valuable server wey you run. E dey hold working credentials for about twelve services inside one file, so you suppose treat am like password manager host: firewall wey exposes 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.

Lock one version before you install anything

Open Connector still new. The repository first show for the first time on 29 June 2026. As of 1 August 2026, the newest tagged release na v1.3.3. Dem publish am on 30 July 2026, and e still get the latest tag. The registry publish tip tag too. Dem build am 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. Then you fit spend the whole evening dey debug am as if na agent problem. Pin the image to a release tag. Upgrade am when you decide, after you read the release notes.

Deploy Open Connector behind TLS for your own VPS

Before the container start, you need:

  • Docker with the Compose plugin, for Ubuntu 24.04 or something wey close to am
  • one hostname wey e A record 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 cover the proxy side. The same certificate setup, from start reach finish for one app, dey inside 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. Neither one get default value, and the runtime fit start 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 down explain why.

Now compose.yaml. E different from the upstream example for two places, and both matter.

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 the published ports enter the NAT (network address translation) table before the ufw filter chain see the packet, so ufw deny 3000 no close that port. The trap dey described for why Docker ports bypass ufw. When you write 127.0.0.1:3000:3000, e publish only for the loopback interface, and your reverse proxy connect from the same host.

The :? mark each variable as required, so the stack refuse to start when .env dey missing, instead of starting with credentials wey no get encryption. Keeping the values inside .env instead of the compose file na the pattern from Docker Compose env files and secrets.

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 read 0.0.0.0:3000, e mean say the port mapping still be the upstream one, and the gateway dey answer the whole internet directly. If the health check return connection refused, e mean the container never listen yet, 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 reach the container through the internal network and nothing need publish to the host at all. certresolver=le must match the resolver name for your Traefik static config, or the router go start without certificate.

Why OAuth dey force you get real hostname

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

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

OAuth provider dey redirect browser back to that URI. This mean say e must be address wey outside world fit reach, and providers dey reject plain http:// for anything wey no be localhost. Na this be the full reason why this deployment need hostname and certificate. Set the origin before the first start, because the value dey read during startup: 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 to 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 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 say 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 dey 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, no be the credential

Agent dey authenticate to the gateway with runtime token, wey admin API dey 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 dey carry token wey dey 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 answer 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, and gateway go offer discovery tools like search_actions and execute_action instead of one tool for each API. This one keeps the agent tool list small. How to run MCP servers for VPS covers the client side of that setup.

Run one more check before you call this work complete. Repeat the action call after you delete the authorization header. The project own quickstart dey call /v1 without any bearer, so installation wey no get runtime auth configured go execute actions for anybody wey fit reach the port. If your unauthenticated call succeeds, you get two options: configure runtime tokens and confirm say the anonymous call now fails, or restrict /api, /v1 and /mcp for the reverse proxy to the addresses wey your agents dey use. Na only /oauth/callback need remain open to everybody, because na the single path wey provider browser redirect needs.

Reduce the action list to wetin the agent need

Gateway wey get one thousand providers behind am na broad surface to give language model. Two controls dey 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 the agent request, and na this one dey separate mistake from incident. Runtime tokens carry their own action rules on top the global ones, and their allowedProxies list dey start empty, so POST /v1/proxy/:service go dey refused until you grant am. That proxy endpoint dey forward raw request to provider with your credential attached, so leave am empty unless one particular agent need am.

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK default na false, wey dey 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 di box wey dey hold every token

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

Stop di container while you dey copy di SQLite file, because copy wey happen during 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

Di volume name na your project directory plus _connector-data. Na why di first command dey there: paste di real name inside di third one. Send di archive comot from di VPS with restic backups from a VPS, wey dey encrypt am before e comot, because that archive na di credential store.

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

Wetin fit break, and di message wey you go see

redirect_uri_mismatch for di provider. Di origin and di registered callback URL no match. Compare di exact string from /api/oauth/configs with di provider app settings, including https against http and any trailing slash.

Every /api call dey return 401. Di admin token header dey miss or you spell am wrong. Di header na Authorization: Bearer <token>, and di web console dey ask for di same token.

Di container dey run, and di credentials dey plain text. Na so e dey happen when OOMOL_CONNECT_ENCRYPTION_KEY no ever reach di container, because di runtime dey store credential records without encryption instead of refusing to start. Prove am for your own install: connect one provider with API key wey you fit recognise, then search di 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 di key no dey active, so check say .env dey inside di same directory with compose.yaml and say docker compose config dey show di value. When you set di key, di same search go return 0, because AES-256-GCM seal di record (advanced encryption standard, 256-bit key, Galois/counter mode).

Nothing dey decrypt after restore. Di encryption key don change or e don lost. E no dey ever write near di data, by design, so no recovery path dey and no support ticket fit help. Connect every provider again. Rotation dey supported through separate key variable and data command for di runtime, so read di current release notes before you rotate anything.

Di agent dey give error wey name action wey e fit see for di catalog. Discovery and execution dey separate. Action fit appear for search_actions and still OOMOL_CONNECT_ALLOWED_ACTIONS fit refuse am, or denylist fit block am, or di runtime token get im own rules.

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

FAQ

I need public domain to self-host Open Connector?

For provider wey dey use API key, no: gateway for 127.0.0.1 dey enough. For OAuth, yes for practical use. Provider go redirect browser go your callback URL, so that URL must resolve from public internet, and providers no dey accept plain http:// outside localhost. 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 fit decrypt the credentials wey dem store, and recovery no dey. The key no dey stored together with the data on purpose, so nobody wey get the database fit read am, including you. Your only option na to set new key and reconnect every provider. Keep the key for password manager and put the database inside your backup rotation, because restore need both.

My AI agent fit see provider access token?

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

Gateway suppose dey reachable from public internet?

Na only /oauth/callback need be reachable. Publish the container port on 127.0.0.1 so Docker NAT rules no fit expose am past your firewall, and put reverse proxy for front. 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 authenticated calls be the only ones wey dey work.

Open Connector ready for production use?

E get Apache 2.0 license and e dey move fast: the repository show for 29 June 2026 and v1.3.3 release come out for 30 July 2026, so treat every version number for this guide as snapshot of 1 August 2026. Run am pinned to release tag, never on latest or tip, read release notes before every upgrade, and keep volume backup wey you don restore once. The design solid for box wey you own, and na version churn be the risk, no be the architecture.