How to Install Langfuse on Ubuntu 24.04 VPS: Self-Hosted LLM Observability and Evaluation
Shipping an LLM-powered product without observability is like running a database without slow-query logs. You can ship, you can even grow, but the first time latency spikes or a customer reports a bad answer you will be flying blind. Langfuse is the open-source answer to that problem: traces, prompt versioning, evaluations, datasets, and session replay, all under a permissive license, all runnable on a single VPS.
This tutorial walks through installing Langfuse v3 on Ubuntu 24.04 LTS with Docker Compose. You will deploy the full production stack (web, async worker, Postgres, ClickHouse, Redis, and MinIO), wire the SDK into OpenAI and Anthropic code, ship your first trace, and put the whole thing behind Nginx with a Let's Encrypt certificate.
Looking for a no-config option? Deploy Langfuse on a CloudCore Professional VPS and follow this guide. The plan's 6 vCPU and 12 GB of RAM comfortably hosts Langfuse plus an application workload.
Table of Contents
What is Langfuse?
Langfuse is an open-source LLM engineering platform. At its core, Langfuse is a tracing backend tuned for generative AI workloads: every call to an LLM provider, every tool invocation, every retrieval step, every user feedback event is captured as a structured observation with inputs, outputs, token counts, latency, cost, and tags. Those observations roll up into traces, traces roll up into sessions, and the whole thing is queryable in a web UI that actually understands what "a prompt" and "a completion" are.
Around that tracing core Langfuse adds four capabilities that matter for production AI:
- Prompt management -- versioned, environment-aware prompt templates with linked traces, A/B labels, and a REST API so that editing a prompt never means redeploying code.
- Evaluations -- run LLM-as-a-judge evaluators, custom Python scorers, or user feedback over production traces or curated datasets. Measure hallucination, helpfulness, toxicity, or any bespoke rubric.
- Datasets and experiments -- turn real traces into regression test sets, then run new prompts or new models against them before shipping.
- Sessions and user analytics -- group traces by user or session ID to see cost per customer, drop-offs inside a conversation, and long-horizon quality drift.
@langfuse/openai), native integrations for LangChain, LlamaIndex, LiteLLM, Vercel AI SDK, Dify, Flowise, and a generic OpenTelemetry ingestion endpoint for anything else. If your stack speaks HTTP, it can send traces to Langfuse.Why Self-Host Langfuse Instead of Langfuse Cloud?
Langfuse Cloud is a fine product, especially for teams that want to get started in minutes without operating any infrastructure. But once you cross a few thresholds, self-hosting on your own VPS starts to win.
- Data residency and privacy -- Every prompt and completion sent through Langfuse is business-sensitive. They often contain customer names, support ticket bodies, medical notes, legal language, or proprietary code. A self-hosted instance keeps those artifacts inside your security perimeter. You pick the country, you pick the encryption, you pick who has access.
- GDPR and regulated industries -- For teams serving EU citizens, healthcare, finance, or government, sending production LLM traffic to a US-based SaaS often requires a DPA review, a sub-processor audit, or simply is not allowed. Running Langfuse yourself sidesteps the entire review.
- Cost at scale -- Langfuse Cloud pricing scales with observations and users. A busy application easily produces millions of observations per month. On your own VPS, the cost is flat: the machine hums along whether you ingest ten events or ten million.
- Unlimited retention -- Cloud plans cap trace retention. Self-hosted Langfuse stores traces as long as your disk has room. Pair it with object storage for large payloads and you can keep years of history for regulatory or quality trending.
- Custom evaluators and SSO -- Self-hosted instances can wire up private evaluator models, bespoke scoring functions, internal identity providers, and custom export pipelines without waiting for a feature request.
- Offline and air-gapped environments -- If your application runs on a private network that cannot call out to a SaaS, a self-hosted Langfuse still works.
Prerequisites
Before starting, you need:
- An Ubuntu 24.04 LTS VPS with root or sudo access
- SSH access and comfort with the Linux command line
- At least 4 vCPU, 8 GB RAM, and 60 GB SSD -- ClickHouse is the heaviest component
- A domain name (for example
langfuse.yourdomain.com) with an A record pointing to the VPS - Ports 80 and 443 open in your firewall for public access via Nginx
- Outbound internet access to pull Docker images
Recommended plan: CloudCore Professional>
The CloudCore Professional plan fits this workload well:>
- 6 vCPU
- 12 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth
- EUR 19.99/month>
That gives headroom for Langfuse plus a small application running alongside it. For ingestion rates above 10 million observations per month, move Postgres or ClickHouse to a dedicated node.
Connect to the VPS:
ssh root@your-server-ipStep 1: Update the System
sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release ufw openssl gitOpen the ports you will need:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enableReboot if the kernel was updated:
sudo rebootReconnect after a minute.
Step 2: Install Docker and Compose
Langfuse is distributed as a Docker Compose stack, so Docker Engine and the Compose plugin are required.
Add Docker's official GPG key and repository:
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine, CLI, and the Compose plugin:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginEnable and start the service:
sudo systemctl enable --now dockerAdd your user to the docker group so you can run commands without sudo:
sudo usermod -aG docker $USERLog out and back in (or run newgrp docker) for the group change to take effect. Verify:
docker --version
docker compose versionExpected output (versions may be newer):
Docker version 26.1.3, build b72abbb
Docker Compose version v2.27.0Step 3: Create the Langfuse Project Directory
Keep all Langfuse files in a single directory so backups and upgrades are simple.
sudo mkdir -p /opt/langfuse
sudo chown $USER:$USER /opt/langfuse
cd /opt/langfuseCreate subdirectories that will hold persistent volumes on the host (optional, Docker will create them, but explicit is better):
mkdir -p postgres-data clickhouse-data clickhouse-logs redis-data minio-dataStep 4: Generate Secrets for the .env File
Langfuse needs three cryptographic values: NEXTAUTH_SECRET (session signing), SALT (password hashing), and ENCRYPTION_KEY (field-level encryption of API keys stored in the database). Generate them with OpenSSL so they are properly random:
echo "NEXTAUTH_SECRET=$(openssl rand -base64 32)"
echo "SALT=$(openssl rand -base64 32)"
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"The ENCRYPTION_KEY must be exactly 64 hex characters (256 bits). The other two are base64 strings of at least 256 bits of entropy.
Now create the .env file. Replace the placeholder passwords and paste the three secrets you just generated:
cat > /opt/langfuse/.env <<'EOF'--- Langfuse application ---
NEXTAUTH_URL=https://langfuse.yourdomain.com NEXTAUTH_SECRET=<paste NEXTAUTH_SECRET here> SALT=<paste SALT here> ENCRYPTION_KEY=<paste ENCRYPTION_KEY here>TELEMETRY_ENABLED=false LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=false
Uncomment to lock signups after you create the first admin:
AUTH_DISABLE_SIGNUP=true
--- Postgres ---
POSTGRES_USER=langfuse POSTGRES_PASSWORD=change-me-postgres POSTGRES_DB=langfuse DATABASE_URL=postgresql://langfuse:change-me-postgres@postgres:5432/langfuse--- ClickHouse ---
CLICKHOUSE_USER=langfuse CLICKHOUSE_PASSWORD=change-me-clickhouse CLICKHOUSE_DB=langfuse CLICKHOUSE_URL=http://clickhouse:8123 CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000 CLICKHOUSE_CLUSTER_ENABLED=false--- Redis ---
REDIS_HOST=redis REDIS_PORT=6379 REDIS_AUTH=change-me-redis--- Object storage (MinIO, S3-compatible) ---
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse LANGFUSE_S3_EVENT_UPLOAD_REGION=auto LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=change-me-minio LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000 LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=change-me-minio LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://minio:9000 LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ EOF
Lock the file down -- it contains every secret in the stack:
chmod 600 /opt/langfuse/.envFinally, open the file in your editor and replace each change-me-* placeholder with a fresh random value (for example openssl rand -hex 16), and paste the three secrets you generated with OpenSSL. Also set NEXTAUTH_URL to the public HTTPS URL you will serve Langfuse on.
Step 5: Write the Docker Compose Stack
Create the Compose file. This deploys the official Langfuse v3 stack: the Next.js web app, the async worker, Postgres for metadata, ClickHouse for high-volume event storage, Redis for queues and caching, and MinIO for blob payloads.
cat > /opt/langfuse/docker-compose.yml <<'EOF' version: "3.9"services: langfuse-web: image: langfuse/langfuse:3 restart: unless-stopped depends_on: postgres: condition: service_healthy clickhouse: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy ports: - "127.0.0.1:3000:3000" env_file: .env environment: LANGFUSE_WORKER_HOST: langfuse-worker LANGFUSE_WORKER_PASSWORD: ${REDIS_AUTH}
langfuse-worker: image: langfuse/langfuse-worker:3 restart: unless-stopped depends_on: postgres: condition: service_healthy clickhouse: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy env_file: .env environment: LANGFUSE_WORKER_PASSWORD: ${REDIS_AUTH}
postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - ./postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10
clickhouse: image: clickhouse/clickhouse-server:24.3 restart: unless-stopped user: "101:101" environment: CLICKHOUSE_USER: ${CLICKHOUSE_USER} CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} CLICKHOUSE_DB: ${CLICKHOUSE_DB} CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1" volumes: - ./clickhouse-data:/var/lib/clickhouse - ./clickhouse-logs:/var/log/clickhouse-server ulimits: nofile: soft: 262144 hard: 262144 healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:8123/ping"] interval: 10s timeout: 5s retries: 10
redis: image: redis:7-alpine restart: unless-stopped command: > redis-server --requirepass ${REDIS_AUTH} --maxmemory 512mb --maxmemory-policy allkeys-lru volumes: - ./redis-data:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_AUTH}", "ping"] interval: 10s timeout: 5s retries: 10
minio: image: minio/minio:latest restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID} MINIO_ROOT_PASSWORD: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY} volumes: - ./minio-data:/data ports: - "127.0.0.1:9001:9001" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 10s timeout: 5s retries: 10
minio-bucket-init: image: minio/mc:latest depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set local http://minio:9000 ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID} ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY}; mc mb --ignore-existing local/${LANGFUSE_S3_EVENT_UPLOAD_BUCKET}; mc anonymous set download local/${LANGFUSE_S3_EVENT_UPLOAD_BUCKET} || true; exit 0; " restart: "no" EOF
A few design choices worth calling out:
- The web UI is bound to
127.0.0.1:3000, not0.0.0.0. Only Nginx (running on the same host) will talk to it, which is exactly what you want. - MinIO's console is also local-only. Port-forward over SSH if you need to browse buckets.
- ClickHouse runs as user
101:101, which matches the image's bundledclickhouseuser and avoids volume permission errors on fresh Ubuntu installs. - The
minio-bucket-initone-shot container provisions thelangfusebucket on first boot so the worker and web app do not fail on startup.
Step 6: Launch the Stack
From /opt/langfuse:
docker compose pull
docker compose up -dWatch the startup logs until the web app is ready:
docker compose logs -f langfuse-webOn first boot the web app runs Prisma migrations against Postgres and ClickHouse. Expect 30-90 seconds before you see a line like:
langfuse-web-1 | ready - started server on 0.0.0.0:3000, url: http://localhost:3000Press Ctrl+C to stop following the logs (the containers keep running). Verify every service is healthy:
docker compose psExpected output:
NAME STATUS PORTS
langfuse-clickhouse-1 Up (healthy) 8123/tcp, 9000/tcp
langfuse-langfuse-web-1 Up 127.0.0.1:3000->3000/tcp
langfuse-langfuse-worker-1 Up
langfuse-minio-1 Up (healthy) 127.0.0.1:9001->9001/tcp
langfuse-postgres-1 Up (healthy) 5432/tcp
langfuse-redis-1 Up (healthy) 6379/tcpFrom your local machine, open an SSH tunnel so you can reach the UI in a browser while TLS is not yet configured:
ssh -L 3000:127.0.0.1:3000 root@your-server-ipNow open http://localhost:3000 in your browser.
Step 7: Create the First Admin, Project, and API Keys
The very first user to sign up on a fresh Langfuse instance automatically becomes the organization owner with full admin permissions.
acme.production-app.Once the first admin is created, lock down signups so no one else can register an account. Edit /opt/langfuse/.env:
AUTH_DISABLE_SIGNUP=trueRestart only the web service:
cd /opt/langfuse
docker compose up -d langfuse-webFrom now on, additional users are invited from inside the org's Members tab.
Step 8: Instrument OpenAI and Anthropic SDKs
With the platform up, the next question is: how do traces actually get in? Langfuse's SDKs have two flavors: a drop-in wrapper that replaces your existing client, and a low-level decorator SDK you can sprinkle on any function.
Install the client libraries in your application:
npm install langfuse @langfuse/openai openai @anthropic-ai/sdkExport the three keys from Step 7 into the application's environment:
export LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxxxxxxxxxxxxxx
export LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxxxxxxxxxxxxxx
export LANGFUSE_BASEURL=https://langfuse.yourdomain.comTracing OpenAI with the drop-in wrapper
@langfuse/openai is a zero-code-change wrapper. Import the OpenAI client from @langfuse/openai instead of openai and every call is automatically traced, including streaming, tool calls, and function calling.
import { OpenAI } from "@langfuse/openai";const openai = new OpenAI();
const completion = await openai.chat.completions.create( { model: "gpt-4o-mini", messages: [ { role: "system", content: "You are a concise Linux sysadmin assistant." }, { role: "user", content: "How do I list open TCP ports on Ubuntu 24.04?" }, ], }, { langfuseUpdateParent: true, langfuseTraceName: "sysadmin-chat", langfuseSessionId: "session-42", langfuseUserId: "user_abc", langfuseTags: ["production", "chat"], } );
console.log(completion.choices[0].message.content);
The second argument is Langfuse-specific and entirely optional -- the wrapper still traces if you omit it. Setting langfuseSessionId and langfuseUserId is what enables session replay and per-user cost reporting later.
Tracing Anthropic with the generic SDK
The Anthropic SDK does not have a drop-in wrapper yet, but the generic Langfuse SDK makes it a two-line change:
import Anthropic from "@anthropic-ai/sdk"; import { Langfuse } from "langfuse";const langfuse = new Langfuse(); const anthropic = new Anthropic();
async function askClaude(userId: string, question: string) { const trace = langfuse.trace({ name: "claude-answer", userId, tags: ["production", "anthropic"], });
const generation = trace.generation({ name: "claude-3-5-sonnet", model: "claude-3-5-sonnet-20241022", input: question, });
const response = await anthropic.messages.create({ model: "claude-3-5-sonnet-20241022", max_tokens: 512, messages: [{ role: "user", content: question }], });
const text = response.content[0].type === "text" ? response.content[0].text : "";
generation.end({ output: text, usage: { input: response.usage.input_tokens, output: response.usage.output_tokens, }, });
await langfuse.flushAsync(); return text; }
Refresh the Langfuse UI and open Traces. You will see a new trace per call with inputs, outputs, token counts, latency, and (once you add pricing config) cost in USD.
Python equivalent
If your stack is Python, the same flow looks like:
pip install langfuse openai anthropicfrom langfuse.openai import OpenAI # drop-in replacementclient = OpenAI()
resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "Hello from Langfuse"}], name="hello-trace", session_id="session-42", user_id="user_abc", )
Step 9: Use Prompts, Evaluations, and Sessions
Tracing is the ground floor. The three features above it are what make Langfuse a full LLM engineering platform.
Prompt management
Stop hard-coding prompts. Create them in Langfuse, reference them by name, and iterate without redeploying.
sysadmin-system, pick type Text, and paste:You are a concise Linux sysadmin expert.
Answer in at most {{max_sentences}} sentences. Use fenced code blocks for commands.latest.Fetch it at runtime:
import { Langfuse } from "langfuse"; const lf = new Langfuse();
const prompt = await lf.getPrompt("sysadmin-system", undefined, { label: "production" }); const compiled = prompt.compile({ max_sentences: "3" });
When you link the prompt to the OpenAI call (pass langfusePrompt: prompt in the wrapper, or prompt.linkTo(generation) in the generic SDK), every trace is attributed to the exact prompt version that produced it. You can then compare quality across prompt versions in the Prompts -> Metrics tab.
Evaluations
Evaluations score traces against a rubric. Langfuse supports three styles:
- User feedback -- capture thumbs-up/down or numeric ratings in your app and post them via
trace.score({...}). - Model-based evaluators -- LLM-as-a-judge running inside Langfuse on a schedule. Go to Evaluations -> New evaluator, pick a template (hallucination, helpfulness, conciseness, toxicity), point it at a trace filter (for example "last 24 hours, production tag"), and let it run.
- Custom scorers -- push arbitrary numeric or categorical scores from your own code:
trace.score({
name: "exact-match",
value: answer === expected ? 1 : 0,
comment: "regression test",
});Scores show up on every trace and aggregate into dashboards under Evaluations.
Sessions
A session ties together all traces that share a sessionId. Chat apps, agents, and multi-step pipelines all benefit from session view. In the UI, open Sessions, click any row, and you see every trace in order with total latency, total cost, total tokens, and a collapsible transcript. This is how you debug long conversations and attribute cost per customer.
Datasets and experiments
When a trace represents a gold-standard example, click Add to dataset and store it. Later, under Datasets -> your-dataset -> Run, you can replay the dataset against any new prompt version or model and compare scores side by side. This is the canonical regression test loop for LLM features.
Step 10: Publish Through Nginx with TLS
Right now Langfuse is only reachable via SSH tunnel. Let's put it behind Nginx with a Let's Encrypt certificate so you and your teammates can browse it from anywhere.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the site config:
sudo tee /etc/nginx/sites-available/langfuse > /dev/null <<'EOF' server { listen 80; server_name langfuse.yourdomain.com; return 301 https://$host$request_uri; }server { listen 443 ssl http2; server_name langfuse.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/langfuse.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/langfuse.yourdomain.com/privkey.pem;
# Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Referrer-Policy strict-origin-when-cross-origin always;
# Large payloads: traces can carry multi-MB prompts client_max_body_size 50m;
location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket/long-polling support for the Langfuse UI proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Streaming and long-running ingest endpoints proxy_buffering off; proxy_read_timeout 600s; proxy_send_timeout 600s; } } EOF
Enable the site, issue the certificate, and reload:
sudo ln -s /etc/nginx/sites-available/langfuse /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo certbot --nginx -d langfuse.yourdomain.com
sudo systemctl reload nginxCertbot installs a systemd timer that auto-renews the certificate every 60 days. Open https://langfuse.yourdomain.com in a browser -- the UI should load over TLS with no warnings.
Finally, set NEXTAUTH_URL in /opt/langfuse/.env to the same HTTPS URL (you may have done this already) and restart the web service so session cookies are issued against the correct origin:
cd /opt/langfuse
docker compose up -d langfuse-webBackups and Operations
Langfuse state lives in four places: Postgres, ClickHouse, Redis, and MinIO. Postgres holds metadata (users, projects, prompts, scores). ClickHouse holds observations. MinIO holds large payload blobs. Redis is just a queue and a cache -- it can be rebuilt.
Nightly backup script
sudo tee /usr/local/bin/langfuse-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd /opt/langfuseSTAMP=$(date +%Y%m%d-%H%M%S)
DEST=/var/backups/langfuse
mkdir -p "$DEST"
docker compose exec -T postgres \
pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" | gzip > "$DEST/postgres-$STAMP.sql.gz"
docker compose exec -T clickhouse \
clickhouse-client --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" \
--query "BACKUP DATABASE $CLICKHOUSE_DB TO Disk('backups','ch-$STAMP.zip')"
tar -czf "$DEST/minio-$STAMP.tar.gz" -C /opt/langfuse minio-data
Keep 14 days
find "$DEST" -type f -mtime +14 -delete
EOF
sudo chmod +x /usr/local/bin/langfuse-backup.shSchedule it via cron:
echo "15 3 * root /usr/local/bin/langfuse-backup.sh >> /var/log/langfuse-backup.log 2>&1" | \
sudo tee /etc/cron.d/langfuse-backupUpgrading
Pin the major version (langfuse/langfuse:3) in Compose, and upgrade by pulling the latest patch release:
cd /opt/langfuse
docker compose pull
docker compose up -dAlways take a backup first. Read the release notes before crossing a major version boundary.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
langfuse-web restarts every 30 seconds | Missing NEXTAUTH_SECRET, SALT, or ENCRYPTION_KEY | Check .env. ENCRYPTION_KEY must be 64 hex chars. Regenerate with openssl rand -hex 32. |
relation "users" does not exist in Postgres logs | Migrations did not run | docker compose logs langfuse-web -- look for the Prisma migration output. Restart with docker compose up -d --force-recreate langfuse-web. |
| ClickHouse container keeps exiting | Volume permissions | Stop stack, sudo chown -R 101:101 /opt/langfuse/clickhouse-data, start again. |
SDK gets 401 Unauthorized | Wrong baseURL or API keys | Verify LANGFUSE_BASEURL has no trailing slash and matches NEXTAUTH_URL. Rotate keys from Settings if unsure. |
| Traces never appear in UI | Worker unhealthy or blob upload failing | docker compose logs langfuse-worker. Confirm MinIO is healthy and the langfuse bucket exists. |
413 Request Entity Too Large | Nginx body limit too low for large prompts | Already set to 50m above. Raise further and reload Nginx. |
| Signup page rejects new users | AUTH_DISABLE_SIGNUP=true is set | Expected after Step 7. Invite from the org Members tab instead. |
| UI logs out every few minutes | NEXTAUTH_URL mismatches the browser URL | Set it to the exact HTTPS origin, restart langfuse-web. |
docker compose logs -f --tail=200FAQ
Is Langfuse a replacement for Datadog or New Relic?
No, it is complementary. Datadog and New Relic are general-purpose APM tools that understand HTTP requests, database queries, and infrastructure metrics. Langfuse understands LLM-specific concepts: prompts, completions, tokens, evaluations, prompt versions, and sessions. Most teams run both: traditional APM for the application tier, Langfuse for the AI tier. You can even link them by propagating trace IDs across both systems.
How much disk does Langfuse use?
ClickHouse is the dominant consumer. A rough rule of thumb is 1 KB per observation after compression. At 10 million observations per month you need about 10 GB per month of ClickHouse storage, plus MinIO growth proportional to prompt and completion sizes. A 100 GB disk comfortably holds a year of data for most teams. For higher ingestion rates, move ClickHouse to block storage and increase the clickhouse-data volume size.
Can I use an external Postgres or managed ClickHouse?
Yes. Point DATABASE_URL at any Postgres 13+ instance and CLICKHOUSE_URL / CLICKHOUSE_MIGRATION_URL at any ClickHouse 24+ cluster. Remove the corresponding services from the Compose file. Managed providers like Supabase (Postgres), Neon (Postgres), or ClickHouse Cloud work without changes to application code.
How do I integrate Langfuse with LangChain or LlamaIndex?
Both have first-class callback handlers. In LangChain JS, add new CallbackHandler({ publicKey, secretKey, baseUrl }) to your chain's callbacks array. In Python, use from langfuse.callback import CallbackHandler and pass the handler into your RunnableConfig. LlamaIndex ships an equivalent LangfuseCallbackHandler. See Langfuse's integrations docs for the latest snippets.
Does Langfuse support multi-tenant deployments?
Yes. Langfuse natively models organizations and projects. One self-hosted instance can host many orgs with their own members, projects, API keys, and prompt libraries. For a SaaS-style deployment where each customer should be isolated at the data layer, you can run one Langfuse instance per customer, or use a single instance with one org per customer and rely on Langfuse's RBAC.
Can I export traces to my data warehouse?
Yes. Langfuse exposes a public API for traces, observations, and scores. Most teams sync nightly into BigQuery, Snowflake, or a ClickHouse cluster with Airbyte, Fivetran, or a small Python job. The self-hosted edition also lets you read ClickHouse directly.
Next Steps
With Langfuse running, here are good follow-ups to build a complete self-hosted AI stack on the same VPS or a neighbor:
- Run local LLMs alongside Langfuse -- Install Ollama on another VPS and trace every call it serves. See How to Install Ollama on Ubuntu 24.04.
- Give your local models a chat UI -- Open WebUI is a polished, self-hosted ChatGPT-style interface that pairs perfectly with Langfuse for tracing. See How to Install Open WebUI on Ubuntu 24.04.
- Build no-code AI workflows -- Flowise lets you assemble LangChain agents with a visual builder, and it has a native Langfuse integration. See How to Install Flowise on Ubuntu 24.04.
- Set up real-time alerts -- Configure Langfuse webhooks or connect its API to Grafana so that spikes in latency, cost, or hallucination score page you automatically.
- Curate a regression dataset -- Start collecting gold-standard traces now. Even 50 examples per feature make prompt changes dramatically safer.
- Read the official docs -- The Langfuse documentation covers OpenTelemetry ingestion, advanced evaluators, SSO with Okta/Azure AD, and production scaling.
Want managed infrastructure without managing the VPS?>
Our CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe for EUR 19.99/month -- enough headroom to run Langfuse, your application, and a local model server on the same box. Pair it with our managed backup add-on and forget about disk failures.>
Spin up a CloudCore Professional VPS and deploy Langfuse in the next 35 minutes.