Read this Kibana overview for a quick theory lesson.

Overview

This page covers the base deploy of Kibana: Docker container on the VPS, talking to the local Elasticsearch, served under the sub-path /logs/ of farnetiandrea.it via nginx.

Public access stays behind a login form: anyone hitting farnetiandrea.it/logs gets the basic auth screen.

The follow-up page “Kibana viewer-mode” layers an anonymous provider on top, so visitors land directly on Discover without a login.

Get the base deploy working first, then layer the viewer mode if you want.

This is what will be exposed, and to whom:

AddressReached fromBehind auth?
https://farnetiandrea.it/logsPublic internet via nginxYes, login form
http://127.0.0.1:5601 (on the VPS)Same host onlyYes, Kibana requires auth

Prerequisites

Installation

1. Generate Kibana credentials

Two values needed:

  • kibana_system password: Kibana authenticates to ES with this built-in service user. It exists in ElasticSearch from day one, but starts with an empty / random password.
  • Encryption key: Kibana encrypts certain saved objects (alerts, reports, connectors). If you don’t set a persistent key, Kibana picks a random one at every restart and previously-encrypted saved objects become unreadable.
echo "KIBANA_SYSTEM=$(openssl rand -hex 24)"
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"

Copy both into your password manager, then append them to /opt/observability-logs/.env:

sudo tee -a /opt/observability-logs/.env > /dev/null <<'EOF'
KIBANA_SYSTEM_PASSWORD=<paste hex value here>
KIBANA_ENCRYPTION_KEY=<paste hex value here>
EOF

2. Add the password of kibana_system user in ElasticSearch

cd /opt/observability-logs
ELASTIC=$(grep '^ELASTIC_PASSWORD=' .env | cut -d= -f2-)
KS=$(grep '^KIBANA_SYSTEM_PASSWORD=' .env | cut -d= -f2-)
 
curl -sX POST -u "elastic:$ELASTIC" \
  -H "Content-Type: application/json" \
  http://localhost:9200/_security/user/kibana_system/_password \
  -d "{\"password\":\"$KS\"}"
echo
 
# Verify
curl -s -o /dev/null -w "kibana_system auth: HTTP %{http_code}\n" \
  -u "kibana_system:$KS" http://localhost:9200/_cluster/health
# kibana_system auth: HTTP 200

3. Add the Kibana service in docker-compose

Append the Kibana service to /opt/observability-logs/docker-compose.yml, next to the existing Elasticsearch service:

A few notes on the choices:

  • SERVER_BASEPATH=/logs + SERVER_REWRITEBASEPATH=true: Kibana itself handles the /logs prefix on every URL it emits and accepts. The reverse-proxy does not strip the prefix, it passes the path through unchanged.
  • SERVER_PUBLICBASEURL=https://farnetiandrea.it/logs: used when generating absolute URLs (sharing links, email reports, etc.). Must match the public URL exactly.
  • 127.0.0.1:5601 binding: Kibana is reachable only from the same host, then the internet talks to nginx, and nginx talks to localhost.
  • depends_on: elasticsearch: condition: service_healthy: Kibana refuses to start cleanly if ES isn’t ready. Waiting for service_healthy (not just service_started) avoids the restart loop.

Create the data dir:

sudo mkdir -p /opt/observability-logs/kibana-data
sudo chown -R 1000:1000 /opt/observability-logs/kibana-data

4. Start Kibana

cd /opt/observability-logs
sudo docker compose up -d kibana
sudo docker compose logs -f kibana

First start takes ~90 seconds (plugin setup + migrations).

Wait for:

[INFO ][status] Kibana is now available

Verify locally:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5601/logs/api/status
# HTTP 200

5. Nginx reverse-proxy at /logs/

Add a location /logs/ block to your existing virtual host in Nginx.

My config file lives at /etc/nginx/sites-available/farnetiandrea.it:

INFO

No trailing slash on proxy_pass (http://127.0.0.1:5601;, not http://127.0.0.1:5601/;).

With a trailing slash, nginx strips the matched prefix /logs/ before forwarding, but Kibana expects to see /logs/ because we set SERVER_REWRITEBASEPATH=true.

Without the trailing slash, nginx passes the full URI through and Kibana handles the prefix itself.

Reload nginx:

sudo nginx -t
sudo systemctl reload nginx

6. Verify public access

Open the URL in a browser. You should see the Welcome to Elastic login form.

Log in with elastic + the password from /opt/observability-logs/.env.

After login you land on the Kibana home.

If you see a 502, check sudo docker compose ps (Kibana healthy?) and sudo docker compose logs kibana (any error?).

7. Create a Data View

A Data View tells Kibana which Elasticsearch indices to expose to “Discover”, “Dashboard”, and the rest of the UI.

Without it, Discover stays empty even when data exists.

Create one as soon as you have an index pattern to point at (typically once a log producer is shipping events).

  1. Log in as elastic.
  2. Stack Management → Data Views → Create data view.
  3. Name: logs. Index pattern: logs-*. Timestamp field: @timestamp.
  4. Save data view to Kibana.

Of course if you are following this guide step-by-step, you won’t see anything, for now.

Where to go next

Go on and check out how to assemble the next piece of the puzzle: the Logstash workers setup!