An agent that works at 3 a.m. with nobody watching is only as good as your ability to reconstruct what it did. "It burned $38 of tokens overnight" is a bad morning. "It burned $38 because a search tool returned an empty page and the planner retried it 400 times" is a bug you can fix by lunch. The second sentence needs traces.
Langfuse records every LLM call, prompt, tool step, token count and latency as a tree you can click through. Running it yourself keeps those prompts and responses — which tend to contain customer data, API output and the occasional secret — on a server you control instead of someone else's dashboard.
What it costs to run
Langfuse v4 isn't one small binary. It's six containers: the web app, a background worker, Postgres, ClickHouse for the events, Redis and MinIO for blobs. We installed it on a test server and measured about 3.5 GB of RAM in use with an empty project. ClickHouse is the part that keeps growing, because every span you send ends up there.
| Workload | Plan |
|---|---|
| A handful of agents, weeks of traces | Medium-IP — 6 vCPU, 6 GB, 45 GB ($20/mo) |
| An agent fleet, months of history | Pro plans, 32 GB of RAM and up — on request |
Smaller plans will install it and then struggle the moment ClickHouse and the worker want memory at the same time. I wouldn't go below 6 GB.
Where do the agents live? If they run on other machines, pick a plan with its own IPv4: they need an HTTPS endpoint, and on a NAT plan the outbound address isn't your own and only the SSH port is reachable from outside. If the agents run on this same server, Langfuse never has to face the internet — Medium on NAT ($12/mo) is enough for a trial. There are two locations, Germany and Finland, and availability is shown at checkout; choose the one closer to the agents, not to you. Servers are paid in crypto, no KYC.
Install
On a fresh Ubuntu 24.04 or Debian 12 server, as root. Docker first:
curl -fsSL https://get.docker.com | sh
Then Langfuse, with real secrets instead of the defaults baked into the compose file:
git clone --depth 1 https://github.com/langfuse/langfuse.git
cd langfuse
PG=$(openssl rand -hex 16); MINIO=$(openssl rand -hex 16)
cat > .env <<EOF
NEXTAUTH_URL=https://langfuse.example.com
NEXTAUTH_SECRET=$(openssl rand -hex 32)
SALT=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$PG
DATABASE_URL=postgresql://postgres:$PG@postgres:5432/postgres
CLICKHOUSE_PASSWORD=$(openssl rand -hex 16)
MINIO_ROOT_PASSWORD=$MINIO
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=$MINIO
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=$MINIO
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=$MINIO
REDIS_AUTH=$(openssl rand -hex 16)
TELEMETRY_ENABLED=false
EOF
chmod 600 .env
Two of those passwords have partners: the Postgres password also sits inside DATABASE_URL, and the MinIO password has to match the three S3_..._SECRET_ACCESS_KEY lines. That's why each comes from one variable.
Now the trap. The compose file publishes the web app on port 3000 and MinIO on 9090 to every interface, and ports published by Docker go around ufw. Bind both to localhost so the reverse proxy is the only way in:
sed -i 's/- 3000:3000/- 127.0.0.1:3000:3000/; s/- 9090:9000/- 127.0.0.1:9090:9000/' docker-compose.yml
docker compose up -d
curl -s http://127.0.0.1:3000/api/public/health
The last command should print {"status":"OK", ...}. The first start runs database migrations; on our test box the health check passed about 15 seconds after the containers came up.
HTTPS in front
Point langfuse.example.com at the server's IP, then:
apt install -y caddy
cat > /etc/caddy/Caddyfile <<'EOF'
langfuse.example.com {
reverse_proxy 127.0.0.1:3000
}
EOF
systemctl reload caddy
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable
Caddy fetches the certificate on its own. Open the site, create your account (it becomes the first organisation), add a project and copy its public and secret keys.
Then close the door behind you. Langfuse understands AUTH_DISABLE_SIGNUP, but the stock compose file doesn't hand it to the web container, so it takes one extra line under langfuse-web:
sed -i 's|^ NEXTAUTH_SECRET: \(.*\)$| NEXTAUTH_SECRET: \1\n AUTH_DISABLE_SIGNUP: ${AUTH_DISABLE_SIGNUP:-false}|' docker-compose.yml
echo "AUTH_DISABLE_SIGNUP=true" >> .env
docker compose up -d
From now on a sign-up attempt gets „Sign up is disabled“. Add teammates from inside the project instead.
Send traces from an agent
The Python SDK wraps your functions, and nested calls become nested spans:
python3 -m venv venv && . venv/bin/activate
pip install langfuse
export LANGFUSE_PUBLIC_KEY=pk-lf-...
export LANGFUSE_SECRET_KEY=sk-lf-...
export LANGFUSE_BASE_URL=https://langfuse.example.com
from langfuse import get_client, observe
langfuse = get_client() # reads the LANGFUSE_* variables
@observe(name="fetch-prices")
def fetch_prices(pair: str) -> dict:
return {"pair": pair, "bid": 64210.5}
@observe(name="nightly-agent-run")
def run() -> str:
data = fetch_prices("BTC/USDT")
return f"done: {data['pair']}"
print(run())
langfuse.flush() # short scripts exit before the background sender finishes
Don't skip LANGFUSE_BASE_URL. Without it the SDK quietly sends everything to Langfuse's own cloud, your keys don't exist there, and you end up staring at an empty dashboard.
To check from the shell that the spans arrived:
curl -s -u "$LANGFUSE_PUBLIC_KEY:$LANGFUSE_SECRET_KEY" \
"$LANGFUSE_BASE_URL/api/public/v2/observations?limit=5"
In v4 the older /api/public/traces endpoint answers with an error about events-only mode. That's expected, not a broken install. In a real agent you wrap the LLM call and each tool the same way; Langfuse also ships integrations for the popular LLM SDKs and agent frameworks, so often it's an import change rather than decorators everywhere.
What to keep an eye on
- Disk. ClickHouse and MinIO keep everything you send.
docker system df -vshows where the space went. If you trace long prompts and responses, 45 GB on Medium-IP fills up in weeks, not years. Check it monthly; when you outgrow it, a Pro plan with 400 GB and more is available on request. - Updates. The compose file pins the major version (
:4).docker compose pull && docker compose up -dbrings in fixes within it. Read the changelog before you change the major version: v4 is exactly where the traces endpoint went away. - Backups. What matters lives in Docker volumes: Postgres holds users, projects and keys, ClickHouse and MinIO hold the traces. Copy them off the server on a schedule.
Is it worth $20 a month? If your agents spend more than that on tokens — and most unattended agents do — knowing where the tokens went pays for itself the first time a loop runs away. One small agent that runs once a day doesn't need any of this; a log file and a token counter will do.
Related
- VPS for AI agents — where the agents themselves run
- VPS for multi-agent systems — sizing a whole fleet
- VPS for a LiteLLM proxy — one gateway for all model keys, with spend limits
- Pro plans on request — 32 to 80 GB of RAM for an agent fleet
- How to configure a ufw firewall
Comments
No comments yet. Be the first.