Private by default
Split your instance into three zones:
Each path covers everything under it. Open the public and cloud AI zones only if you use the features behind them, see Public paths. Otherwise, keep the whole instance private. AI assistants that run on your team’s computers, like Claude Code, need nothing opened, see AI clients.
What you give up: hosted automation tools that call the Twenty API from their own cloud (Zapier, Make, n8n Cloud) can’t reach a private instance. Run them inside your network instead (self-hosted n8n works well), or use Twenty workflows. Don’t make
/graphql or /rest public to work around this: the app itself runs on those endpoints, so exposing them exposes everything.Choose a setup
Tailscale
Fully private. Simplest setup, best for small teams.
Cloudflare
No open ports, login with your identity provider, public paths where needed.
Reverse proxy
Caddy or Nginx with IP allowlists. No third-party service.
Option 1: Tailscale
Tailscale puts your server and your team’s devices on a private network. Twenty gets an HTTPS address that only works for people on that network.1
Bind Twenty to localhost
In
docker-compose.yml, change the server port mapping so the container is only reachable from the server itself:2
Join the server to your tailnet
Install Tailscale on the server, then:
3
Serve Twenty over HTTPS
https://twenty.<your-tailnet>.ts.net, with a certificate managed by Tailscale. If the command asks you to, enable HTTPS certificates for your tailnet in the admin console.4
Update SERVER_URL
docker compose up -d.Option 2: Cloudflare Tunnel and Access
A Cloudflare Tunnel connects your server to Cloudflare with an outbound connection, so no ports are open on the server. Cloudflare Access then asks your team to log in with your identity provider before any request reaches Twenty.1
Protect the hostname with Access
Do this before creating the tunnel, so the hostname is never reachable without a login. Create self-hosted Access applications. Access applies the most specific path, so the path rules below win over the hostname rule.
Create one application per path. Only add the bypass rules for features you use.Local AI clients like Claude Code can’t complete a browser login, so they rely on the office or VPN IPs in the last rule. If your team works from anywhere, add an Allow policy for your team to that application instead, enable WARP authentication in Access, and have them run the Cloudflare WARP client.
2
Create a tunnel
In the Cloudflare Zero Trust dashboard, create a tunnel, pick Docker as the environment and copy the token. Add a public hostname,
crm.example.com, pointing to http://server:3000.3
Run cloudflared next to Twenty
Add this service to Add
docker-compose.yml and remove the ports section from server. cloudflared reaches Twenty over the Docker network, so the server doesn’t need to publish any port.CLOUDFLARE_TUNNEL_TOKEN to your .env.4
Update SERVER_URL
docker compose up -d.Option 3: Your own reverse proxy
If you already run a reverse proxy, allowlist your office or VPN IP addresses for the app and open only the public paths. Bind Twenty to localhost first, as in option 1, so nothing bypasses the proxy.- Caddy
- Nginx
203.0.113.0/24 with your office or VPN addresses. CHATGPT_CONNECTOR_IPS holds OpenAI’s ranges, see AI clients; leave it empty if you don’t use ChatGPT. If your proxy sits behind a load balancer, match on the client IP it forwards (client_ip with trusted_proxies in Caddy, set_real_ip_from in Nginx), not on the load balancer’s address.
Public paths
These are the paths that services outside your network call. Open only the ones you need.
By default Twenty polls mailboxes and calendars, so Google and Microsoft never call your instance. They only do if you set
IS_CONNECTED_ACCOUNT_WEBHOOK_SUBSCRIPTION_ENABLED=true.
How each public path is protected:
- Workflow webhooks only check that the workspace and workflow exist. Anyone who knows the URL can trigger the workflow, so treat the URL as a secret and validate the payload in the workflow before acting on it.
- App and logic function routes are public unless the route requires authentication (
isAuthRequired). For public routes, verify the caller in your own code, for example with the sending service’s signature. - Google, Microsoft, SES and Resend webhooks are verified by Twenty, using the provider’s signature or token.
- Email links can only record an open, a click or an unsubscribe for the recipient they were sent to.
AI clients (MCP)
AI assistants are one of the main ways your team will use Twenty, through its MCP server. What you need to open depends on where the assistant runs, not on which assistant it is.Local clients: nothing to open
Claude Code, Codex CLI, Cursor and other tools that run on your computer connect to Twenty from that computer. If it’s on your tailnet or VPN, they reach/mcp the same way your browser reaches the app. This is the safest setup, so prefer it when you can.
Claude Desktop is the exception: connectors added in Settings → Connectors run in Anthropic’s cloud, even in the desktop app. To keep the connection local, add Twenty as a local MCP server in claude_desktop_config.json instead:
Cloud connectors: open with care
Claude (web, mobile, and connectors in the desktop app) and ChatGPT call your server from Anthropic’s and OpenAI’s infrastructure, not from your device. They can’t reach a private instance, so you have to open/mcp, /oauth and /.well-known to their IP ranges.
What keeps them out is login. To get a token, a user has to sign in on Twenty’s /authorize page, which stays in the private zone. Someone outside your network can start the connection flow but can’t finish it, and /mcp rejects every request without a valid token.
What stays exposed is the code that runs before login: OAuth discovery, client registration (/oauth/register) and the token endpoint. The allowlist narrows that: internet-wide scanners can’t reach these paths directly, and requests have to come through Claude’s or ChatGPT’s infrastructure. It doesn’t make that code safe on its own, so upgrades still matter.
If you use cloud connectors:
- Open only
/mcp,/oauthand/.well-known. Never open/authorizeor the rest of the app. - Allow only the vendors you use:
-
Anthropic:
160.79.104.0/21, from Anthropic’s IP addresses page. -
OpenAI: the list in chatgpt-connectors.json. OpenAI describes it as best effort and it changes over time, so refresh it on a schedule:
-
Anthropic:
- Upgrade quickly when release notes mention MCP or OAuth fixes.
Verify your setup
Test from both sides of your network after any change:https://crm.example.com/.well-known/.... If it shows http:// or 127.0.0.1, your proxy isn’t forwarding the Host and X-Forwarded-Proto headers.
Baseline checklist
Postgres and Redis are not published. The default
docker-compose.yml doesn’t publish them; keep it that way if you edit it.Nothing bypasses your proxy or tunnel. With a proxy on the host, Twenty’s port is bound to
127.0.0.1. With cloudflared in Docker, the server service publishes no port. Docker writes its own firewall rules, so ports it publishes skip ufw and similar host firewalls.Secrets are long and random, and you know how to rotate them.
Workspace members use SSO or two-factor authentication.
The database is backed up on a schedule, and you have tested a restore.
TAG is pinned to a version, and you upgrade on purpose.Stay up to date
None of the above replaces upgrading. It buys you time.- Watch releases on GitHub: Watch → Custom → Releases.
- When a release includes a security fix, upgrade as soon as its advisory says to: the same day for critical issues. Follow the upgrade guide.
- Report vulnerabilities to security@twenty.com, not in public issues.