OpenObserve on Ubuntu: dedicated server, S3, GCS, OCI, and MinIO

Mascote LinuxPro administrando o OpenObserve em um servidor Ubuntu com armazenamento de observabilidade

After getting to know the OpenObserve and its unified platform for logs, metrics, and traces, the next step is to operate it on a dedicated Ubuntu server without Docker. This guide installs the official binary as a service systemd, keeps the OpenObserve port on loopback, publishes the interface via nginx with TLS, and shows when to use local disk or S3, Google Cloud Storage, Oracle Cloud Object Storage, and MinIO.

Scope: is a single-node installation. It is suitable when a maintenance window and a tested restoration are acceptable. High availability is a different architecture: use the official HA design with the required components and perform a staging validation before moving important data.

Architecture and sizing

Scenario Metadata Telemetry data When to use
Dedicated server with disk Local SQLite Local disk in Parquet Small or medium environment, simple operation and limited retention.
Small server + object storage Local SQLite S3, GCS, Oracle Object Storage or MinIO When capacity and durability must exceed the VM disk.
High availability PostgreSQL Object storage Critical service, horizontal scaling, and fault tolerance.

Local mode is the default: uses SQLite for metadata and can write stream data to disk or S3-compatible storage. For a single-node bucket, set ZO_LOCAL_MODE_STORAGE=s3. Do not change the backend of an instance with data without a migration plan, backup, and restore test: pointing to a bucket does not automatically migrate existing Parquet files.

Useful metadata Initial profile Suggested disk Recommended use
Up to 500 GB 4 vCPU, 16 GB RAM 2 × 1 TB NVMe in RAID 1 Few services, host metrics, and light production.
Up to 1 TB 8 vCPU, 32 GB RAM 2 × 2 TB NVMe in RAID 1 Multiple services, dashboards, and moderate queries.
Up to 4 TB 12–16 vCPU, 64 GB RAM 2 × 8 TB NVMe in RAID 1 Higher local retention; monitor I/O, compression, and backup.
Small server + bucket 4–8 vCPU, 16–32 GB RAM 200–500 GB NVMe local + bucket SQLite, WAL, and temporary local data; Parquet in object storage.

These profiles are starting points, not a capacity guarantee. Measure actual daily ingestion, run a pilot for at least one week, and adjust retention. Reserve space for WAL, compaction, and cache: by default, disk cache can use up to 50% of free space. RAID 1 reduces the impact of a disk failure, but does not replace backup against deletion, logical corruption, or compromised credentials.

1. Prepare Ubuntu and create the service user

Update the system and install only utilities used to download, validate, and extract the archive. The OpenObserve process runs under a system account without login; the binary remains under root control and data is located in /var/lib/openobserve.

sudo apt update
sudo apt install -y ca-certificates curl openssl tar

sudo adduser --system --group --home /var/lib/openobserve \
  --shell /usr/sbin/nologin openobserve
sudo install -d -o root -g root -m 0755 /usr/local/lib/openobserve
sudo install -d -o openobserve -g openobserve -m 0750 /var/lib/openobserve
sudo install -d -o root -g openobserve -m 0750 /etc/openobserve

Do not use an administrative shell account for the service. Separation allows limiting who reads credentials and who writes to local data.

2. Download and install the official binary

First consult the official downloads page and replace OO_VERSION with a stable release that you have qualified for production. The example uses v0.92.2, available in the official downloads repository on 11 2026. It detects amd64 or arm64; for machines with old glibc, the documentation recommends using the package linux-amd64-musl, with possible performance cost.

export OO_VERSION=v0.92.2
case "$(dpkg --print-architecture)" in
  amd64|arm64) OO_ARCH="$(dpkg --print-architecture)" ;;
  *) echo "Arquitetura não suportada"; exit 1 ;;
esac

OO_TARBALL="openobserve-${OO_VERSION}-linux-${OO_ARCH}.tar.gz"
OO_URL="https://downloads.openobserve.ai/releases/openobserve/${OO_VERSION}/${OO_TARBALL}"

cd /tmp
curl -fL -o "$OO_TARBALL" "$OO_URL"
sha256sum "$OO_TARBALL"  # compare com o SHA-256 exibido na página Downloads

rm -rf /tmp/openobserve-install
mkdir /tmp/openobserve-install
tar -xzf "$OO_TARBALL" -C /tmp/openobserve-install
sudo install -o root -g root -m 0755 \
  /tmp/openobserve-install/openobserve \
  "/usr/local/lib/openobserve/openobserve-${OO_VERSION}"
sudo ln -sfn "/usr/local/lib/openobserve/openobserve-${OO_VERSION}" \
  /usr/local/bin/openobserve

/usr/local/bin/openobserve --version

The checksum is a mandatory step before executing a downloaded binary. Keep the installed version: updates should download a new release, validate the hash, install a different file, and only then swap the symbolic link during a maintenance window.

3. Create the environment file

OpenObserve is configured via environment variables. Create a file that only root and the service group can read. The root password is used on first initialization; generate a long password, save it to a vault, and replace the example values before starting the service.

sudo tee /etc/openobserve/openobserve.env > /dev/null <<'EOF'
ZO_ROOT_USER_EMAIL=admin@example.com
ZO_ROOT_USER_PASSWORD=TROQUE_POR_UMA_SENHA_LONGA_E_ALEATORIA

# Nó único, dados no disco local
ZO_LOCAL_MODE=true
ZO_LOCAL_MODE_STORAGE=disk
ZO_DATA_DIR=/var/lib/openobserve

# O nginx será a única entrada pública
ZO_HTTP_ADDR=127.0.0.1
ZO_HTTP_PORT=5080
ZO_GRPC_ADDR=127.0.0.1
ZO_WEB_URL=https://o2.exemplo.com
ZO_CORS_ALLOWED_ORIGINS=https://o2.exemplo.com

# Ajuste a retenção ao piloto e ao espaço disponível
ZO_COMPACT_DATA_RETENTION_DAYS=30
EOF
sudo chown root:openobserve /etc/openobserve/openobserve.env
sudo chmod 0640 /etc/openobserve/openobserve.env

# Gere uma senha para copiar ao cofre; depois cole-a no arquivo acima.
openssl rand -base64 36

ZO_HTTP_ADDR=127.0.0.1 avoids publishing port 5080 directly. We also define ZO_GRPC_ADDR=127.0.0.1 so the default gRPC port isn't exposed on the network. If OpenObserve is served on a subpath, like https://exemplo.com/logs/, use ZO_BASE_URI=/logs and adjust the proxy.

4. Run as systemd service

Create the unit. The file descriptor limit follows the official guide's recommendation; Restart=on-failure restarts the process after a failure, without turning a configuration error into a silent loop.

[Unit]
Description=OpenObserve
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=openobserve
Group=openobserve
WorkingDirectory=/var/lib/openobserve
EnvironmentFile=/etc/openobserve/openobserve.env
ExecStart=/usr/local/bin/openobserve
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true

[Install]
WantedBy=multi-user.target
sudo tee /etc/systemd/system/openobserve.service > /dev/null <<'EOF'
[Unit]
Description=OpenObserve
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=openobserve
Group=openobserve
WorkingDirectory=/var/lib/openobserve
EnvironmentFile=/etc/openobserve/openobserve.env
ExecStart=/usr/local/bin/openobserve
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now openobserve
sudo systemctl status openobserve --no-pager
curl -fsS http://127.0.0.1:5080/healthz; echo
sudo journalctl -u openobserve -n 100 --no-pager

The expected response from the health check is {"status":"ok"}. If the service doesn't start, confirm permissions in /var/lib/openobserve, the environment file syntax and the log of the journalctl. Do not change the root password on an already-initialized instance hoping to recreate the user: these variables are only needed on the first start.

5. Install nginx and publish with TLS

After validating OpenObserve on loopback, publish it with its own DNS name, for example o2.exemplo.com. Before requesting the certificate, create A records (and AAAA if using IPv6) pointing to the server and confirm that ports 80 and 443 reach it. OpenObserve stays on 127.0.0.1:5080; only nginx will be exposed.

Install the reverse proxy

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo nginx -t
sudo systemctl status nginx --no-pager

If the host uses UFW, first preserve administrative access and allow only HTTP/HTTPS. In cloud providers, make the same release in the provider's firewall or security group.

# Execute somente se UFW já faz parte da política deste host.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw status verbose

First create an HTTP virtual host. It allows verifying the proxy and handling the Let's Encrypt HTTP-01 challenge. Do not publish port 5080 or 5081 in the firewall.

sudo tee /etc/nginx/sites-available/openobserve >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name o2.exemplo.com;

    location / {
        proxy_pass http://127.0.0.1:5080;
        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;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/openobserve /etc/nginx/sites-enabled/openobserve
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
curl -fsSI http://o2.exemplo.com/

Use a subdomain, as in the example. If you choose to publish under a path, like https://exemplo.com/logs/, follow the OpenObserve documentation and define ZO_BASE_URI=/logs; just adding location /logs/ in nginx is not sufficient.

Issue and renew the certificate

With DNS propagated and port 80 publicly accessible, Certbot's nginx plugin can obtain the certificate and install the HTTPS block. Make a backup of the configuration before letting Certbot modify it.

sudo cp -a /etc/nginx /etc/nginx.backup.$(date +%F)
sudo certbot --nginx --redirect -d o2.exemplo.com
sudo nginx -t
sudo systemctl reload nginx
sudo certbot renew --dry-run
curl -fsSI https://o2.exemplo.com/

The --redirect redirects HTTP to HTTPS. The test renew --dry-run confirms renewal before the expiration date. For corporate certificates or DNS-01 validation, don't run the above command: install the certificate according to organizational policy and keep the same proxy for 127.0.0.1:5080.

ss -lntp | grep -E ':(80|443|5080|5081)\b'

The result should show nginx at 80/443 and OpenObserve ports 5080 and 5081 only on loopback, never in 0.0.0.0. Do not expose the interface, ingestion APIs, or the MCP endpoint over plain HTTP.

6. Use S3, GCS, Oracle Cloud or MinIO

With object storage, SQLite and WAL remain on local disk. Edit the same file /etc/openobserve/openobserve.env, change ZO_LOCAL_MODE_STORAGE to s3, add a provider block and restart the service. Create the bucket first, use a unique prefix per environment, and test writing, reading, and retention in a staging environment.

AWS S3

On EC2, prefer an IAM Role with minimal access to the bucket and prefix instead of static credentials. For standard S3, do not set ZO_S3_SERVER_URL; the SDK looks up the instance identity. Outside of AWS, inject the keys via a secrets manager or the environment file with permission 0640; never include them in Git.

# Em EC2 com IAM Role: não defina AWS_ACCESS_KEY_ID nem AWS_SECRET_ACCESS_KEY
ZO_LOCAL_MODE_STORAGE=s3
ZO_S3_PROVIDER=s3
ZO_S3_REGION_NAME=us-east-1
ZO_S3_BUCKET_NAME=openobserve-producao
ZO_S3_BUCKET_PREFIX=openobserve/producao/
# Fora da AWS: entregue estes valores por cofre de segredos
AWS_ACCESS_KEY_ID=CHAVE_DE_ACESSO
AWS_SECRET_ACCESS_KEY=SEGREDO_DE_ACESSO
ZO_LOCAL_MODE_STORAGE=s3
ZO_S3_PROVIDER=s3
ZO_S3_REGION_NAME=us-east-1
ZO_S3_BUCKET_NAME=openobserve-producao
ZO_S3_BUCKET_PREFIX=openobserve/producao/

Google Cloud Storage

GCS can use the S3-compatible API. Create an HMAC key with restricted access to the bucket and use HTTP/1, as required by the documented configuration:

ZO_LOCAL_MODE_STORAGE=s3
ZO_S3_SERVER_URL=https://storage.googleapis.com
ZO_S3_REGION_NAME=auto
ZO_S3_ACCESS_KEY=CHAVE_HMAC_GCS
ZO_S3_SECRET_KEY=SEGREDO_HMAC_GCS
ZO_S3_BUCKET_NAME=openobserve-producao
ZO_S3_BUCKET_PREFIX=openobserve/producao/
ZO_S3_FEATURE_HTTP1_ONLY=true
ZO_S3_PROVIDER=s3

Direct integration also exists: use ZO_S3_PROVIDER=gcs and, when there is no instance identity, provide in ZO_S3_ACCESS_KEY the path to the service account JSON key, with read-only permission for the user openobserve.

Oracle Cloud Object Storage

Oracle Object Storage offers an S3-compatible API. Use a Customer Secret Key, keep the URL in your tenancy and region format, and validate the configuration in a test bucket before writing production data.

ZO_LOCAL_MODE_STORAGE=s3
ZO_S3_SERVER_URL=https://SEU_NAMESPACE.compat.objectstorage.SUA_REGIAO.oci.customer-oci.com
ZO_S3_REGION_NAME=SUA_REGIAO
ZO_S3_ACCESS_KEY=OCI_ACCESS_KEY
ZO_S3_SECRET_KEY=OCI_CUSTOMER_SECRET_KEY
ZO_S3_BUCKET_NAME=openobserve-producao
ZO_S3_BUCKET_PREFIX=openobserve/producao/
ZO_S3_PROVIDER=s3
ZO_S3_FEATURE_FORCE_HOSTED_STYLE=false

MinIO

MinIO is an S3-compatible option for datacenter, another VM, or Kubernetes. Create the bucket before starting and use TLS between OpenObserve and the endpoint. A MinIO on the same host does not create redundancy: a machine failure can take down both application and data at the same time.

ZO_LOCAL_MODE_STORAGE=s3
ZO_S3_SERVER_URL=https://minio.exemplo.com
ZO_S3_REGION_NAME=us-east-1
ZO_S3_ACCESS_KEY=MINIO_ACCESS_KEY
ZO_S3_SECRET_KEY=MINIO_SECRET_KEY
ZO_S3_BUCKET_NAME=openobserve-producao
ZO_S3_BUCKET_PREFIX=openobserve/producao/
ZO_S3_PROVIDER=minio
sudoedit /etc/openobserve/openobserve.env
sudo systemctl restart openobserve
sudo journalctl -u openobserve -f

7. Integrate AI agents and LLM applications

Yes: OpenObserve can participate in an LLM flow in two ways. The first is observing the AI application via OpenTelemetry — tokens, latency, model, errors, and tool calls become traces and metrics. The second is connecting an MCP client to query logs, metrics, and traces in natural language.

The MCP endpoint follows the format https://o2.exemplo.com/api/ID_DA_ORGANIZACAO/mcp. The documentation indicates support in Open Source, Enterprise, and Cloud. On an HTTPS instance, a test with Claude Code can follow this pattern; generate the token only on the operator's machine and do not register it in repositories, chat, or shared files.

read -r -p "E-mail do usuário MCP: " O2_EMAIL
read -r -s -p "Senha do usuário MCP: " O2_PASSWORD; echo
O2_TOKEN=$(printf '%s' "$O2_EMAIL:$O2_PASSWORD" | base64 -w0)

claude mcp add --scope user openobserve \
  https://o2.exemplo.com/api/default/mcp \
  -t http \
  --header "Authorization: Basic $O2_TOKEN"

unset O2_PASSWORD O2_TOKEN
claude mcp list
Security in MCP: in the Open Source edition, MCP calls use the full privileges of the connected account; granular RBAC is available in Enterprise and Cloud. In OSS, use a dedicated account for traceability and rotation, but don't treat it as minimum privilege. Restrict the endpoint by network/IP, require confirmation for write operations, and consider any text coming from logs as untrusted data — including against prompt injection.

AI telemetry may contain prompts, responses, identifiers, and retrieved documents. Define beforehand which attributes can leave the application, perform redaction when necessary, and align retention, LGPD, and access. Do not send secrets or API keys in logs or spans.

Operation, backup and update

  • Monitor the service itself: CPU, RAM, I/O, free space, ingestion errors, query time, and bucket growth.
  • Protect the metadata: on single node, local SQLite contains configurations, users, dashboards, and alerts; make consistent backup of /var/lib/openobserve and the environment file, keeping secrets protected.
  • Test restoration: recovering only Parquet in the bucket does not automatically recreate users, dashboards, or rules.
  • Update with rollback: install a new version side by side in /usr/local/lib/openobserve, validate the SHA-256, update the link /usr/local/bin/openobserve and restart. Keep the previous version until full validation.
  • Review retention: ZO_COMPACT_DATA_RETENTION_DAYS has a pattern of 3650 days; define an explicit policy compatible with the bucket lifecycle rules.

Final checklist

  • Official binary installed and SHA-256 verified before execution.
  • Service openobserve active, with writable data only by the dedicated account.
  • Ports 5080 and 5081 bound to loopback; interface available only in HTTPS via nginx.
  • Root password and bucket credentials stored outside Git, chat, and unencrypted backups.
  • Retention calculated with actual ingestion, not with a presumed compression ratio.
  • Bucket write/read test and metadata restoration completed before production.

Official sources