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.
Open Connector dey do wetin for AI agent
To self-host Open Connector na to put one auth gateway between your AI agents and every software as a service (SaaS) API wey dem dey call. This one mean say agent no go ever hold provider token. Na open source gateway from OOMOL Lab, and Apache 2.0 license cover am. E dey run as one container, keep im state for one SQLite file, and expose provider actions through 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, and e go enter the transcript too. Na this failure how to keep secrets away from AI agents dey describe.
Auth gateway dey split the credential into two parts. The gateway stores provider credential and runs the OAuth flow. Agent gets runtime token wey only valid against the gateway. When agent calls one action, gateway loads the stored credential, injects am into the outbound request on the server side, and returns only the response body. Agent no ever receive provider access token. So if agent transcript leak, wetin you lose na one runtime token wey you fit revoke, instead of your GitHub account.
The catalog dey 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, 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 dey 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 keep 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 normally remain valid even after you change password. If dem breach, dem fit breach you too. Self-hosting move these records go SQLite for machine wey you rent and administer, and e seal dem with key wey never comot your box.
Talk the cost clearly before you start. This VPS go become the most valuable server wey you dey run. E hold working credentials for about twelve services inside one file, so you need treat am like password manager host: firewall wey expose only 443, no shared logins, backup wey you don actually 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 for am too.
Pin version before you install anything
Open Connector still dey young. Repository first show for 29 June 2026, and as of 1 August 2026 the newest tagged release na v1.3.3, wey dem publish on 30 July 2026 and wey also get the latest tag. Registry publish tip tag too, wey dem build from the newest commit for main.
For project wey new reach this level, 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 like say na agent problem. Pin the image to release tag, then upgrade when you decide to, 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 close to am
- a hostname wey get A record pointing to this VPS, for example
connect.example.com - a reverse proxy wey already dey terminate TLS (transport layer security) for that hostname
- two random secrets, wey you go generate below
The Traefik reverse proxy for plenty Docker Compose apps guide cover the proxy side. The same certificate setup, from start to finish for one app, dey for the n8n for VPS with Docker and HTTPS guide.
Generate the secrets first. The encryption key dey protect the stored credentials. The admin token dey protect the web console and the whole /api surface. None of dem 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 .envCopy 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 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 na the port. The upstream file publish 3000:3000, wey bind every interface for the host. Docker write the published ports into the NAT (network address translation) table before the ufw filter chain fit see the packet, so ufw deny 3000 no close that port. why Docker ports bypass ufw describe this trap. Writing 127.0.0.1:3000:3000 publish for the loopback interface only, and your reverse proxy connect from the same host.
The :? mark every 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 for .env instead of inside the compose file follow the pattern for 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 come up. 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 say 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 dey 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 come up without certificate.
Why OAuth need real hostname
OOMOL_CONNECT_ORIGIN na the setting wey people dey skip, and once dem skip am, OAuth go break in a way wey fit look like provider bug. The runtime dey build e redirect URI from that origin, for the form <origin>/oauth/callback. If you leave am unset, the origin go default to http://localhost:3000. Because of that, the runtime go send redirect URI of 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. This means say the URI must be an address wey outside world fit reach. Providers reject plain http:// for anything wey no be localhost. Na this be the whole 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 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 a runtime token, never the 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 carry 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 answer na envelope wey get success field as 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 offer discovery tools like search_actions and execute_action instead of one tool for each API. This one keep the agent tool list small. Running MCP servers on a VPS cover the client side of this setup.
Run one more check before you call the work complete. Repeat the action call after you delete authorization header. The project's own quickstart 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 succeed, you get two options: configure runtime tokens and confirm say the anonymous call don fail now, or restrict /api, /v1 and /mcp for reverse proxy to the addresses wey your agents dey come from. Na only /oauth/callback need remain open to everybody, because na the single path wey provider browser redirect need.
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 connectorThe 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.sqliteCount 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 to self-host Open Connector?
For providers wey dey use API key, no: gateway for 127.0.0.1 dey enough. But 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 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 dey stored, and recovery no dey. The key no dey stored together with the data on purpose, so anybody wey get 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 include the database for your backup rotation, because restore need both.
My AI agent fit see provider access token?
No, not when e dey call through the gateway. The agent authenticates with runtime token wey start with oct_, and gateway dey inject provider credential into outbound request for the server, then 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 suppose dey 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 in 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 alone dey work.
Open Connector don ready for production use?
E get Apache 2.0 license and development dey move fast: repository show up on 29 June 2026 and v1.3.3 release happen on 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 good for box wey you own. The main risk na version churn, no be the architecture.