Observability Azure

Trickster 2.0 Dashboard Accelerator on Ubuntu 24.04 on Azure User Guide

| Product: Trickster 2.0 HTTP Reverse Proxy Cache and Dashboard Accelerator on Ubuntu 24.04 on Azure

Overview

This guide covers the deployment and use of Trickster 2.0 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images.

Trickster is an open source HTTP reverse proxy cache and time series dashboard accelerator. It sits between your dashboards and your time series database, understands the query language of backends such as Prometheus, InfluxDB and ClickHouse, and caches the results of the range queries that dashboards issue on every refresh. When a panel reloads, Trickster serves the portion of the time range it already holds from cache and asks the database only for the newly elapsed slice, instead of re fetching the whole window. It also collapses concurrent identical requests into a single origin fetch, so a wall of dashboards opening at once no longer stampedes the backend.

A Prometheus is bundled on this same VM and wired up as the default origin. Trickster is a caching proxy, so on its own it has nothing to accelerate. This image ships Prometheus 3.13.2 alongside it, listening on the loopback interface and already scraping both itself and Trickster, which means the appliance answers real queries with real data the moment first boot finishes. You are not required to keep it: repointing Trickster at your own Prometheus, InfluxDB or ClickHouse is a single line of configuration and a reload, covered later in this guide.

Trickster has no web interface. It is a proxy: HTTP requests in, cached responses out. Everything here is done with curl and standard HTTP clients, or by pointing Grafana at it as an ordinary Prometheus data source. The bundled Prometheus does have its own web UI, and this guide shows how to reach it safely over an SSH tunnel.

Security by design. A caching proxy sitting in front of a metrics store is a data exposure shape, so this image is locked down by default:

  • Exactly one port is public, and it is authenticated. TCP 8480 carries the accelerated query API, and every backend route on it is protected by Trickster's own HTTP Basic authenticator using a bcrypt credential generated uniquely on your VM at first boot. An unauthenticated request, or one with the wrong password, is refused with HTTP 401.

  • The only unauthenticated path is the liveness probe. GET /trickster/ping returns the four character response pong and nothing else, so an Azure Load Balancer or Application Gateway health probe works without a credential. It discloses no version, no hostname, no configuration and no data.

  • Everything else is bound to the loopback interface. The Trickster metrics endpoint on 8481, the management endpoint on 8484 that prints the running configuration, and the bundled Prometheus on 9090 including its web UI are all reachable only from the VM itself or through an SSH tunnel.

  • Nothing is baked into the image. The captured image contains no configuration file and no credential at all. Trickster physically cannot start until first boot has generated your credential, so there is no window in which the proxy is running unprotected and no shared password between customers.

What is included:

  • Trickster 2.0.5 from the official upstream release, verified against a pinned SHA 256 checksum at build time, running under systemd as trickster.service

  • Prometheus 3.13.2 from the official upstream release, also SHA 256 verified, running as prometheus.service and bound to the loopback interface as the default origin

  • A 512 MB in memory result cache, and Trickster's Delta Proxy Cache engine which fetches only the newly elapsed slice of a repeated range query

  • A per VM HTTP Basic credential generated on first boot and written to a root only credentials file

  • trickster-selfcheck, a bundled command that verifies the whole appliance end to end, including that the proxy is genuinely serving from cache

  • The upstream Trickster example configurations and documentation on the box under /usr/share/doc/trickster/

  • Unattended security upgrades left enabled so the appliance keeps receiving patches

Prerequisites

  • Active Azure subscription, an SSH public key, and a VNet and subnet in the target region

  • Subscription to this listing on Azure Marketplace

  • A Network Security Group allowing TCP 22 for administration and TCP 8480 for the accelerated query API. In production, restrict 8480 to the subnet your dashboards run in. No other port needs to be opened, because every other service on this appliance is bound to the loopback interface.

Recommended virtual machine size: Standard_B2s with 2 vCPU and 4 GB RAM suits development and light dashboard workloads. For higher query rates or a larger cache working set, choose Standard_D2s_v5 or above.

Deploy the virtual machine

Deploy from the Azure Portal by selecting the image from Azure Marketplace, choosing your VM size, and supplying your SSH public key for the azureuser account. Or deploy from the Azure CLI.

These commands run on your own workstation, not on the VM:

az vm create \
  --resource-group my-resource-group \
  --name my-trickster-vm \
  --image <this-marketplace-image> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

az vm open-port --resource-group my-resource-group --name my-trickster-vm --port 8480

Connect over SSH once the VM is running:

ssh azureuser@<vm-ip>

Confirm the services are healthy

Both services start automatically on boot. Check them and confirm the versions:

systemctl is-active prometheus trickster
trickster --version
prometheus --version 2>&1 | head -1

Expected output

active
active
Trickster version: 2.0.5 (linux/amd64), buildInfo: 2026-07-31T17:10:27+0000 43f88baa43118d9c07991b857d06511ed919004c, goVersion: go1.26.5, copyright: © 2018 The Trickster Authors
prometheus, version 3.13.2 (branch: HEAD, revision: bb5dff00cf8fdfbf5c65e0531aa835fa238a43a2)

Now confirm the network posture. Exactly one port is bound to all interfaces, and the other three are bound to the loopback address only:

ss -tln | grep -E ':(8480|8481|8484|9090) '

Expected output

LISTEN 0      4096       127.0.0.1:8481      0.0.0.0:*
LISTEN 0      4096       127.0.0.1:8484      0.0.0.0:*
LISTEN 0      4096       127.0.0.1:9090      0.0.0.0:*
LISTEN 0      4096               *:8480            *:*

That is the shape you want: 8480 reachable from your dashboards, and the metrics endpoint, the configuration endpoint and Prometheus itself unreachable from outside the VM.

Trickster and Prometheus both reporting active under systemd, with only the accelerated query port bound to all interfaces and the metrics, management and Prometheus ports bound to loopback

Retrieve your per VM credentials

First boot generates the HTTP Basic credential for this VM and writes it to a root only file. Read it first, because the password is shown in plain text only here:

sudo cat /root/trickster-credentials.txt

The file records the accelerated query URL, the username and password, the loopback only endpoints, and copy ready example commands.

Verify the whole appliance end to end at any time with the bundled self check. It proves that unauthenticated and wrong password requests are refused, that your credential is accepted, that the local origin is returning real data, that a repeated range query is genuinely served from cache, and that the metrics, management and Prometheus ports refuse connections on the VM's own network address:

sudo trickster-selfcheck

Expected output

Trickster appliance self-check
  [ok] liveness: /trickster/ping returns pong
  [ok] unauthenticated query -> HTTP 401 (refused)
  [ok] wrong password -> HTTP 401 (refused)
  [ok] authenticated instant query -> 2 live series from the local Prometheus
  [ok] range query -> 30 data points returned
  [ok] repeat range query served from cache -> X-Trickster-Result: engine=DeltaProxyCache; status=hit; ffstatus=off
  [ok] port 8481: reachable on 127.0.0.1, refused on 10.0.0.10 (loopback only)
  [ok] port 8484: reachable on 127.0.0.1, refused on 10.0.0.10 (loopback only)
  [ok] port 9090: reachable on 127.0.0.1, refused on 10.0.0.10 (loopback only)
SELFCHECK OK — appliance authenticated, locked down and accelerating.

The address shown on the last three lines is your own VM's private address, so it will differ from the example above.

The per VM credentials file and the trickster selfcheck command reporting an authenticated, locked down and accelerating appliance

The liveness probe is the only unauthenticated path

Health probes need to work without a credential, so /trickster/ping is deliberately left open. It returns four characters and reveals nothing else:

curl -s http://localhost:8480/trickster/ping

Expected output

pong

Point your Azure Load Balancer or Application Gateway health probe at this path on port 8480.

Everything else requires the credential

Ask the accelerated API for actual data without a credential, and then with the wrong password. Both are refused:

curl -s -o /dev/null -w 'no credential: HTTP %{http_code}\n' 'http://localhost:8480/api/v1/query?query=up'
curl -s -o /dev/null -w 'wrong password: HTTP %{http_code}\n' -u 'cloudimg:WRONG' 'http://localhost:8480/api/v1/query?query=up'

Expected output

no credential: HTTP 401
wrong password: HTTP 401

An unauthenticated query and a wrong password query both refused with HTTP 401, while the per VM credential returns live series data through the proxy

Query through the accelerator

Read the credential straight out of the root only file so you never have to paste a password by hand. Trickster is API compatible with Prometheus, so the familiar query endpoints work unchanged:

TPASS=$(sudo grep '^trickster.password=' /root/trickster-credentials.txt | cut -d= -f2-)
curl -s -u "cloudimg:$TPASS" 'http://localhost:8480/api/v1/query?query=up' | jq -r '.data.result[] | "\(.metric.job) \(.metric.instance) = \(.value[1])"'

Expected output

prometheus 127.0.0.1:9090 = 1
trickster 127.0.0.1:8481 = 1

Those two series come from the bundled Prometheus, which scrapes itself and Trickster. Both reporting 1 means both targets are up and the whole chain from your client, through Trickster, to the origin is working.

Watch the cache actually accelerate

This is what Trickster is for. Issue a range query, then issue exactly the same query again, and compare the X-Trickster-Result response header:

TPASS=$(sudo grep '^trickster.password=' /root/trickster-credentials.txt | cut -d= -f2-)
END=$(date -u +%s); START=$(( END - 900 ))
RANGE="http://localhost:8480/api/v1/query_range?query=up&start=${START}&end=${END}&step=60"
echo "first request:"
curl -s -D- -o /dev/null -u "cloudimg:$TPASS" "$RANGE" | grep -i '^x-trickster-result' | tr -d '\r'
echo "same request again:"
curl -s -D- -o /dev/null -u "cloudimg:$TPASS" "$RANGE" | grep -i '^x-trickster-result' | tr -d '\r'

Expected output

first request:
X-Trickster-Result: engine=DeltaProxyCache; status=kmiss; ffstatus=hit
same request again:
X-Trickster-Result: engine=DeltaProxyCache; status=hit; ffstatus=hit

The first request reports kmiss, meaning the result was not cached at all and was fetched from the origin. The second reports hit, meaning it was served entirely from Trickster's cache and the origin was never asked.

If you have already run a similar query on this VM you will see phit on the first request instead of kmiss. That is a partial hit, and it is the case that matters most in production: as time moves forward Trickster serves everything it already holds from cache and fetches only the newly elapsed seconds, which is where most of the saving on a live dashboard comes from. A phit line also carries a fetched=[...] field naming the exact window it had to go to the origin for.

The response body is identical either way, so the data your dashboard receives is unchanged:

TPASS=$(sudo grep '^trickster.password=' /root/trickster-credentials.txt | cut -d= -f2-)
END=$(date -u +%s); START=$(( END - 900 ))
curl -s -u "cloudimg:$TPASS" "http://localhost:8480/api/v1/query_range?query=up&start=${START}&end=${END}&step=60" | jq '.data.result[0].values | length'

Expected output

5

The number of data points depends on how long your VM has been running and on the step you asked for, so expect a different figure on your own instance.

A range query reporting a cache miss on the first request and a cache hit on an identical repeat request, proving the Delta Proxy Cache engine is accelerating queries

Connect Grafana

Trickster speaks the Prometheus HTTP API, so Grafana treats it as an ordinary Prometheus data source. In Grafana, add a Prometheus data source and set:

Field Value
URL http://<vm-ip>:8480
Authentication Basic authentication
User the trickster.username value from your credentials file
Password the trickster.password value from your credentials file

Save and test. Every panel query now flows through the accelerator, and repeat refreshes of the same dashboard panel are served from cache rather than re querying the backend.

Watch the cache statistics

Trickster publishes its own Prometheus format metrics on the loopback interface, and the bundled Prometheus is already scraping them. Read them directly on the VM:

curl -s http://127.0.0.1:8481/metrics | grep -E '^trickster_cache_operation_objects_total' | head -4

Expected output

trickster_cache_operation_objects_total{cache_name="default",operation="get",provider="memory",status="hit"} 7
trickster_cache_operation_objects_total{cache_name="default",operation="setDirect",provider="memory",status="none"} 3

The exact counters depend on how many queries your instance has served. Because the bundled Prometheus scrapes these metrics, you can also graph them through the accelerator itself, which makes the appliance self observing.

Reach the bundled Prometheus web UI

Prometheus is bound to the loopback interface on purpose: it has no authentication of its own, so exposing it would publish your metrics to anyone who found the port. Reach its web UI with an SSH tunnel instead.

Run this on your own workstation, not on the VM:

ssh -L 9090:127.0.0.1:9090 azureuser@<vm-ip>

Leave that session open and browse to http://127.0.0.1:9090/ on your workstation. You get the full Prometheus UI, including the expression browser, the targets page and the alerting views, without exposing anything.

Confirm on the VM that both scrape targets are healthy:

curl -s http://127.0.0.1:9090/api/v1/targets | jq -r '.data.activeTargets[] | "\(.labels.job) \(.health)"'

Expected output

prometheus up
trickster up

Accelerate your own upstream instead

The bundled Prometheus exists so the appliance is useful and provable the moment it boots. To put Trickster in front of a database you already run, edit the backend section of the configuration:

sudo nano /etc/trickster/trickster.yaml

Change the origin_url under backends: default: to your own endpoint. To accelerate a different kind of backend, change provider as well. Trickster supports prometheus, influxdb, clickhouse, reverseproxycache and reverseproxy, among others:

backends:
  default:
    provider: prometheus
    origin_url: http://my-prometheus.internal:9090
    listener_name: default
    is_default: true
    authenticator_name: cloudimg_basic
    cache_name: default

Leave authenticator_name in place so the accelerated port stays protected by your per VM credential. Apply the change without dropping in flight connections:

sudo systemctl reload trickster

If you no longer need the bundled Prometheus once you are pointed at your own backend, stop it and reclaim its memory and disk:

sudo systemctl disable --now prometheus

Trickster keeps running, because the bundled Prometheus is only its default origin, not a dependency of the proxy itself. The upstream example configurations for other backend types are on the box under /usr/share/doc/trickster/conf/, and the full upstream documentation is under /usr/share/doc/trickster/docs/.

Review the running configuration

The management endpoint prints the exact configuration Trickster is running, which is useful after an edit. It is bound to the loopback interface because that printout includes your origin addresses:

curl -s http://127.0.0.1:8484/trickster/config | head -20

Confirm from the VM that the same endpoint is genuinely unreachable on the VM's own network address:

HOST_IP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -m 5 -w 'off box: HTTP %{http_code}\n' "http://${HOST_IP}:8484/trickster/config" || echo 'off box: connection refused'

Expected output

off box: HTTP 000

000 means curl could not establish a connection at all, which is the correct result.

Server components

Component Version Purpose
Trickster 2.0.5 HTTP reverse proxy cache and time series dashboard accelerator
Prometheus 3.13.2 Bundled local time series database, the default origin
Ubuntu Server 24.04 LTS Base operating system

Filesystem layout

Path Size Purpose
/ 29 GB Root filesystem
/boot 2 GB Operating system kernel files
/boot/efi 100 MB UEFI boot partition (Gen2 Hyper V)
/mnt varies Azure temporary resource disk

Key directories

Path Purpose
/usr/local/bin/trickster The Trickster binary
/etc/trickster/trickster.yaml Trickster configuration, generated on first boot
/etc/trickster/users.htpasswd Your per VM bcrypt credential
/usr/local/bin/prometheus The Prometheus binary
/etc/prometheus/prometheus.yml Prometheus scrape configuration
/var/lib/prometheus/data Prometheus time series database
/usr/share/doc/trickster/ Upstream licence, README, example configs and documentation
/root/trickster-credentials.txt Per VM credentials, root only

Network ports

Port Bound to Authentication Purpose
22 all interfaces SSH key Administration
8480 all interfaces HTTP Basic, per VM credential Accelerated query API. /trickster/ping is the only unauthenticated path
8481 loopback only none needed Trickster metrics in Prometheus format
8484 loopback only none needed Management endpoint and running configuration printout
9090 loopback only none needed Bundled Prometheus, including its web UI

Managing the services

systemctl status trickster --no-pager
systemctl status prometheus --no-pager

Reload Trickster after a configuration change without dropping connections, or restart it outright:

sudo systemctl reload trickster

Read the logs:

sudo journalctl -u trickster -n 30 --no-pager

Scripts and log files

Path Purpose
/usr/local/sbin/trickster-firstboot.sh Generates the per VM credential and configuration on first boot
/usr/local/sbin/trickster-selfcheck End to end verification of the appliance
/var/log/cloudimg-firstboot.log First boot log
/stage/scripts/BUILD_VERSIONS Pinned upstream versions and checksums used at build time
/var/lib/cloudimg/trickster-firstboot.done First boot completion sentinel
/var/lib/cloudimg/trickster-ready Marker that permits trickster.service to start

On startup

The first time the VM boots, trickster-firstboot.service runs once. It waits for the bundled Prometheus to become ready, generates a unique HTTP Basic password, writes it as a bcrypt hash to /etc/trickster/users.htpasswd, writes /etc/trickster/trickster.yaml, creates the trickster-ready marker, starts trickster.service, records everything in /root/trickster-credentials.txt, and disables itself.

trickster.service carries a ConditionPathExists on the trickster-ready marker, which is why it cannot start before that credential exists. The captured image ships without the marker, without a configuration file and without a credential, so there is no boot in which the accelerated port is served unprotected. On every later boot the marker is present and both services start normally.

Confirm first boot completed:

ls -l /var/lib/cloudimg/
sudo tail -5 /var/log/cloudimg-firstboot.log

Troubleshooting

Every request returns HTTP 401, including with the password from the credentials file. Confirm you are sending the username as well as the password, and that you copied the value after the = with no trailing whitespace. Re read it programmatically to rule out a copy error:

sudo grep -E '^trickster\.(username|password)=' /root/trickster-credentials.txt

trickster.service is inactive and will not start. The service is gated on the first boot marker. Check whether it exists:

ls -l /var/lib/cloudimg/trickster-ready
systemctl status trickster --no-pager

If the marker is missing, first boot did not complete. Read /var/log/cloudimg-firstboot.log for the reason, then re run it:

sudo /usr/local/sbin/trickster-firstboot.sh

Queries return an empty result set. The bundled Prometheus needs a few scrape intervals before it holds data. Confirm both targets are up and give it a minute:

curl -s http://127.0.0.1:9090/api/v1/targets | jq -r '.data.activeTargets[] | "\(.labels.job) \(.health)"'

Repeat queries never report a cache hit. Trickster caches by query and time range, so any change to the query string, the step, or the requested window produces a new cache key. Compare two genuinely identical requests. Also confirm the cache is in use:

curl -s http://127.0.0.1:8481/metrics | grep -c '^trickster_cache_operation'

A configuration edit stopped the service. Trickster validates its configuration on startup. Check the file before reloading:

sudo /usr/local/bin/trickster -validate-config -config /etc/trickster/trickster.yaml

The bundled Prometheus is not reachable from your workstation. That is intended. It is bound to the loopback interface. Use the SSH tunnel shown earlier rather than opening port 9090.

Security recommendations

  • Restrict port 8480 to the subnet your dashboards run in. The port is authenticated, but there is no reason to expose it to the whole internet. Use a Network Security Group rule scoped to your Grafana subnet.

  • Terminate TLS in front of the appliance. HTTP Basic authentication sends the credential base64 encoded, not encrypted, so put an Azure Application Gateway or a TLS terminating load balancer in front of port 8480 for anything crossing an untrusted network.

  • Never open ports 8481, 8484 or 9090. They are bound to the loopback interface deliberately. The management endpoint prints your full running configuration including origin addresses, and the bundled Prometheus has no authentication of its own. Use SSH tunnels.

  • Keep the credentials file root only. /root/trickster-credentials.txt is mode 0600 and owned by root. Copy the password into your secret manager and leave the file alone.

  • Rotate the credential when staff change. Write a new bcrypt entry and reload:

sudo htpasswd -B /etc/trickster/users.htpasswd cloudimg
sudo systemctl reload trickster
  • Leave unattended security upgrades enabled so the appliance keeps receiving operating system patches.

Support

cloudimg provides 24/7 support for this image. Visit www.cloudimg.co.uk or contact support through the Azure Marketplace listing.

Trickster and Prometheus are both open source projects released under the Apache License 2.0. Their licence texts ship on the image at /usr/share/doc/trickster/LICENSE and /usr/share/doc/prometheus/LICENSE.