TLS and public access
claude.ai connects to your server from Anthropic’s infrastructure, so for claude.ai the server must be reachable from the internet over HTTPS with a publicly trusted certificate. Claude Code and other local clients can also use a certificate you trust yourself.
How the server picks its certificate
At startup, in this order:
INSECURE_HTTP=true: plain HTTP, no TLS. Use only when TLS is terminated in front of the server on a network you trust.TLS_CERT_PATHandTLS_KEY_PATHboth set: the server loads them, and reloads them automatically when the files change (for example after a certificate renewal). If either file is missing or invalid the server refuses to start; it never falls back silently. Setting only one of them is an error. In this mode responses also carryStrict-Transport-Security.- Neither set: the server generates an in-memory self-signed certificate for the
PUBLIC_URLhost,localhost,127.0.0.1and::1. It is never written to disk, changes on every restart, and its SHA-256 fingerprint is logged.
The self-signed certificate is fine for local use, for Claude Code with the certificate trusted, or for encrypting the hop from a tunnel or reverse proxy that skips verification. It does not work for claude.ai directly.
Option A: a Cloudflare tunnel
The simplest way to get a trusted public address. The tunnel’s edge certificate is trusted by claude.ai, and the server’s self-signed certificate protects the hop from the tunnel to the server. See the Docker Compose example in Getting started. In the tunnel’s public hostname settings, point the service to https://mcp-twenty-crm:8443 and turn on No TLS Verify.
Set PUBLIC_URL to the tunnel hostname, for example https://crm-mcp.example.com.
Option B: a reverse proxy
Run the server behind nginx, Caddy or Traefik with a trusted certificate at the proxy:
- Proxy to
https://<server>:8443(verification off, or pinned to the logged fingerprint), or setINSECURE_HTTP=trueif the proxy and server share a trusted network. - Pass the
Hostheader through unchanged and do not buffer responses (MCP uses streaming HTTP responses). - Set
TRUSTED_PROXIESto the proxy’s IP address so the server uses the client address fromX-Forwarded-Forfor sign-in rate limiting.
The server does not limit request rates itself. If you add a limit at the proxy, limit per bearer token rather than per client address on /mcp, /crm, /token and /register: hosted clients such as claude.ai reach the server from a few shared addresses, so a per-address limit there lets one busy person block everyone. Per address is fine on /authorize, the only page a person’s browser opens.
Option C: the server holds the certificate
Give the server a certificate from Let’s Encrypt or another public CA:
docker run -d --name mcp-twenty-crm -p 443:8443 \
-v /etc/letsencrypt/live/crm-mcp.example.com:/certs:ro \
-e TLS_CERT_PATH=/certs/fullchain.pem \
-e TLS_KEY_PATH=/certs/privkey.pem \
--env-file app.env \
ghcr.io/trozz/mcp-twenty-crm:latest
Renewals are picked up without a restart.
A trusted local certificate
For Claude Code or testing on your own machine.
With mkcert:
brew install mkcert
mkcert -install
mkcert -cert-file cert.pem -key-file key.pem localhost 127.0.0.1 ::1
TLS_CERT_PATH=$PWD/cert.pem TLS_KEY_PATH=$PWD/key.pem ./mcp-twenty-crm
With openssl:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 825 \
-keyout key.pem -out cert.pem -subj "/CN=crm-mcp.example.com" \
-addext "subjectAltName=DNS:crm-mcp.example.com,DNS:localhost,IP:127.0.0.1"
Add cert.pem to the trust store of the machine running the client, or to NODE_EXTRA_CA_CERTS for Node based clients.
The connection to Twenty
Separately from the certificate your server presents, the server’s own connection to Twenty must be https (with certificate verification), except to a loopback address such as http://127.0.0.1:3000 when Twenty runs on the same host. Redirects from Twenty are not followed: set TWENTY_API_URL to the address that answers directly.