Setup guide

Setting Up OmniMem on Linux with a Cloudflare Tunnel

This guide puts OmniMem on a permanent address on your own domain, say https://mcp.example.com, through a named Cloudflare Tunnel. It isn't a quick tunnel: those hand out a new trycloudflare.com name every time cloudflared restarts, so claude.ai loses the connector and you're back to editing settings. A named tunnel keeps one DNS record pointing at it for good.

Like Tailscale Funnel, it needs no open ports, no port forwarding and no certificates to renew. cloudflared dials out to Cloudflare, Cloudflare terminates TLS, and requests reach OmniMem over plain http on localhost. It works the same from a cloud VM, a home server behind NAT or a Raspberry Pi.

The plan: install the OmniMem .deb, create a tunnel, give it a hostname, switch on OAuth.


Why a Cloudflare Tunnel?

The cost is that your domain has to use Cloudflare's nameservers, and Cloudflare sees the traffic in the clear, since it terminates TLS. If that second point bothers you, the Tailscale guide or Caddy on a cloud machine (AWS, Google Cloud) keep TLS on your own box.


What you need

Throughout, mcp.example.com stands for the hostname you'll use. Swap in your own.


Step 1: Install OmniMem

Download the package for your machine from the releases page. On Debian, Ubuntu or Raspberry Pi OS:

sudo apt install ./omnimem_<version>_amd64.deb     # or _arm64.deb

On Fedora and friends:

sudo dnf install ./omnimem-<version>.x86_64.rpm    # or .aarch64.rpm

You get a systemd service (omnimem.service, running omnimem serve as the omnimem user), configuration in /etc/omnimem/omnimem.env, and data in /var/lib/omnimem.

Don't start it yet, it needs to know its address first.


Step 2: Install cloudflared

On Debian, Ubuntu and Raspberry Pi OS, from Cloudflare's own repository:

sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared

For Fedora and others, Cloudflare's downloads page has an .rpm and plain binaries.

cloudflared --version

Step 3: Create the tunnel

There are two ways to manage a named tunnel: from the Cloudflare dashboard, or from a config file on the machine. Both give you a permanent hostname. I use the config file here because the whole setup then lives in two files you can read and back up. The dashboard way is at the end of this step if you'd rather click.

Log in

cloudflared tunnel login

It prints a URL. Open it, sign in to Cloudflare and pick the domain you want to use. That writes an account certificate to ~/.cloudflared/cert.pem, which lets cloudflared create tunnels and DNS records in that zone.

Create it

cloudflared tunnel create omnimem

This prints the tunnel's UUID and writes its credentials to ~/.cloudflared/<UUID>.json. That file is what lets this machine run this tunnel, so treat it like a password. Check it's there:

cloudflared tunnel list

Give it a hostname

cloudflared tunnel route dns omnimem mcp.example.com

That creates a proxied CNAME, mcp.example.com pointing at <UUID>.cfargotunnel.com, in your zone. This is the persistent bit: the record stays put however often the tunnel or the machine restarts.

Write the config

The service will run as root, so put everything under /etc/cloudflared rather than in your home folder:

sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/<UUID>.json /etc/cloudflared/
sudo chmod 600 /etc/cloudflared/<UUID>.json
sudo nano /etc/cloudflared/config.yml
tunnel: <UUID>
credentials-file: /etc/cloudflared/<UUID>.json

ingress:
  - hostname: mcp.example.com
    service: http://127.0.0.1:8765
  - service: http_status:404

The last rule is the catch-all cloudflared insists on: anything that isn't for your hostname gets a 404. Pass the whole path through, as here. OmniMem needs /mcp, /healthz, /.well-known/..., /authorize, /token and the rest to arrive, so don't add a path: that only matches /mcp.

Check the file before going further:

sudo cloudflared tunnel --config /etc/cloudflared/config.yml ingress validate
sudo cloudflared tunnel --config /etc/cloudflared/config.yml ingress rule https://mcp.example.com/mcp

The second should name the http://127.0.0.1:8765 rule.

Run it as a service

sudo cloudflared --config /etc/cloudflared/config.yml service install
sudo systemctl enable --now cloudflared
systemctl status cloudflared

From now on the tunnel comes up at boot. You can delete ~/.cloudflared/cert.pem once you're happy, it's only needed to create tunnels and DNS records, not to run them.

Or: from the dashboard

In the Cloudflare dashboard, go to Zero Trust → Networks → Tunnels, create a tunnel of type Cloudflared, and name it omnimem. It shows an install command with a token in it:

sudo cloudflared service install <token>

Run that on the machine. Then, on the tunnel's Public Hostname tab, add mcp.example.com with the service type HTTP and URL 127.0.0.1:8765. Cloudflare creates the DNS record for you. The token is the tunnel's credentials, so keep it out of screenshots and chat logs.


Step 4: Configure OmniMem

sudo nano /etc/omnimem/omnimem.env

OAuth, for claude.ai

OAUTH_ENABLED=true
OAUTH_BASE_URL=https://mcp.example.com
OAUTH_ADMIN_USER=admin
OAUTH_ADMIN_PASSWORD=pick-a-strong-password-here

https://, your hostname, no trailing slash, no port. OmniMem checks every request's Host against it, and cloudflared passes the original mcp.example.com through, so the two match.

An access token, for everything else

Claude Code, Cursor and the other local agents are simpler with a token:

MCP_AUTH_TOKEN=paste-a-long-random-token-here

Generate one with openssl rand -hex 32. /mcp takes the token or an OAuth sign-in, whichever the client sends.

Mistral's Le Chat (optional)

Le Chat calls /mcp from its own web page, so it needs its origin allowed:

MCP_ALLOWED_ORIGINS=https://chat.mistral.ai

The rest

ANTHROPIC_API_KEY=sk-ant-your-key-here    # optional

Leave MCP_HOST at 127.0.0.1. cloudflared connects over localhost, so OmniMem never needs to listen anywhere else.

Optional OAuth tuning, with the defaults:

# OAUTH_REFRESH_MAX_DAYS=30          # how long before you sign in again (at most 90)
# OAUTH_REFRESH_GRACE_SECONDS=120    # how long a replaced refresh token keeps working
# OAUTH_LOGIN_MAX_ATTEMPTS=10        # failed logins from one address before it's refused
# OAUTH_LOGIN_WINDOW_SECONDS=900     # for this long

About that login limit: through the tunnel every request reaches OmniMem from 127.0.0.1, so the limit counts everyone together rather than per visitor. Ten wrong passwords inside 15 minutes, from anyone, shuts the login page for a while. Fine for a personal server, and it's also what stops someone guessing your password from the internet.


Step 5: Start it

sudo systemctl enable --now omnimem
journalctl -u omnimem -f

The first start downloads the embedding model, about 90 MB. If OAuth is on but a setting is missing, OmniMem refuses to start and the log says which, which beats finding out later from a vague claude.ai error.


Step 6: Check it end to end

From any machine, not just the server:

curl https://mcp.example.com/healthz
curl https://mcp.example.com/.well-known/oauth-authorization-server
curl -i -X POST https://mcp.example.com/mcp

The first should answer {"status": "ok"}, the second a JSON document listing the OAuth endpoints on https://mcp.example.com, and the third 401 with a WWW-Authenticate header naming your hostname. An empty 401 body is correct: it's asking for a sign-in.

If the first one fails, cloudflared can't reach OmniMem or isn't running (systemctl status cloudflared). A 502 from Cloudflare usually means OmniMem itself isn't up yet.


Step 7: Connect claude.ai

  1. Go to claude.ai
  2. Open Settings → Connectors
  3. Add a custom connector with the URL:
https://mcp.example.com/mcp
  1. claude.ai sends you to OmniMem's login page. Sign in with the admin username and password from step 4
  2. You land back in claude.ai with OmniMem connected

Tokens refresh on their own, and because the address never changes, the connector keeps working across reboots and tunnel restarts. After 30 days (OAUTH_REFRESH_MAX_DAYS) you sign in again. More in the claude.ai guide.


Step 8: Connect Claude Code (optional)

The same address works from anywhere:

claude mcp add --transport http omnimem https://mcp.example.com/mcp \
  --header "Authorization: Bearer your-token-here" \
  --scope user

and allow the tools in ~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "mcp__omnimem__*"
    ]
  }
}

On the server itself you can skip Cloudflare and use http://127.0.0.1:8765/mcp with the same token.


Cloudflare settings that get in the way

Most of Cloudflare's defaults are fine. A few of its security features aren't, because they answer some requests with a challenge page, and an MCP client or OAuth server can't solve a challenge. It just sees HTML where it expected JSON and gives up.

Cloudflare doesn't need any special setting for MCP's streamed responses; cloudflared passes them through as they arrive.


RSS feeds (optional)

sudo -u omnimem nano /var/lib/omnimem/feeds.yml
feeds:
  - name: "Rust Blog"
    url: "https://blog.rust-lang.org/feed.xml"
    topics: ["rust", "programming"]
  - name: "The Cloudflare Blog"
    url: "https://blog.cloudflare.com/rss/"
    topics: ["networking", "cloudflare"]

OmniMem notices the change. See RSS and knowledge.


Backups and updates

Back up nightly with the CLI:

sudo crontab -u omnimem -e
0 2 * * * /usr/bin/omnimem --db /var/lib/omnimem/omnimem.db export /var/lib/omnimem/backups/omnimem-$(date +\%Y\%m\%d).json

Copy them somewhere off the machine, and keep a copy of /etc/cloudflared too: with the credentials file you can bring the same tunnel and hostname up on a new machine without touching DNS.

To update OmniMem, install the new package over the old one and restart; the database migrates itself:

sudo apt install ./omnimem_<new-version>_amd64.deb
sudo systemctl restart omnimem

cloudflared updates through apt with everything else.


Running it in Docker instead

The same idea works with the Docker image and Cloudflare's cloudflared image side by side. This uses a tunnel made in the dashboard (step 3's second way), with its public hostname pointing at http://omnimem:8765:

services:
  omnimem:
    image: richarvey/omnimem:<version>
    environment:
      MCP_HOST: 0.0.0.0
      OAUTH_ENABLED: "true"
      OAUTH_BASE_URL: https://mcp.example.com
      OAUTH_ADMIN_USER: admin
      OAUTH_ADMIN_PASSWORD: ${OAUTH_ADMIN_PASSWORD}
      MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN}
    volumes:
      - omnimem-data:/data
    restart: unless-stopped

  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    depends_on:
      - omnimem
    restart: unless-stopped

volumes:
  omnimem-data:

Put OAUTH_ADMIN_PASSWORD, MCP_AUTH_TOKEN and TUNNEL_TOKEN in a .env file beside it. There's no ports: on either service: nothing is published on the host, and the tunnel is the only way in. MCP_HOST: 0.0.0.0 is needed so the cloudflared container can reach OmniMem across the Compose network, and OAuth is what allows it to start that way.


Troubleshooting

https://mcp.example.com says the host can't be found: the DNS record isn't there. cloudflared tunnel route dns omnimem mcp.example.com again, or check DNS → Records for a proxied CNAME to <UUID>.cfargotunnel.com. If tunnel login picked a different domain, the record went into that zone.

Cloudflare error 1033: Cloudflare has the hostname but no cloudflared connected for that tunnel. systemctl status cloudflared and journalctl -u cloudflared -e.

Cloudflare 502 Bad Gateway: the tunnel is up but OmniMem isn't answering on 127.0.0.1:8765. systemctl status omnimem, and check the service: line in config.yml.

OmniMem won't start after switching on OAuth: journalctl -u omnimem -e. A missing OAUTH_BASE_URL, username or password is named in the log, as is a base URL that isn't https.

421 Misdirected Request: OmniMem doesn't recognise the host it was reached on. OAUTH_BASE_URL must be exactly https://mcp.example.com, no trailing slash, no port. If you set httpHostHeader in the ingress rule, remove it: OmniMem needs the real hostname.

403 Forbidden Origin: a browser-based client is calling from an origin OmniMem doesn't know. Add it to MCP_ALLOWED_ORIGINS, as for Le Chat in step 4.

claude.ai fails after you sign in, or never shows the login page: first check the end-to-end tests in step 6 from outside. If they pass, look for blocked requests under Security → Events in the Cloudflare dashboard; a bot or WAF rule challenging claude.ai's servers shows up there. See "Cloudflare settings that get in the way" above.

Signed out after a month: that's OAUTH_REFRESH_MAX_DAYS doing its job. Raise it, up to 90, if you'd rather sign in less often.

Moving from a quick tunnel: if you had MCP_ALLOWED_HOSTS=*.trycloudflare.com and OAUTH_BASE_URL=...,* for a trycloudflare.com address, drop them. With a fixed hostname, OAUTH_BASE_URL=https://mcp.example.com is all OmniMem needs, and remove and re-add the claude.ai connector on the new address.

By Ric HarveyUpdated All guidesThis guide on Forgejo