Grafana Pyroscope on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Grafana Pyroscope 2.2.1 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Grafana Pyroscope is an open source continuous-profiling platform: your applications continuously send profiling data (CPU time, memory allocations, goroutines, locks and more) to Pyroscope, which stores it efficiently and renders it as interactive flamegraphs in a built-in web UI, so you can see exactly which functions consume your resources and how that changes over time.
The image installs the official Pyroscope v2.2.1 release binary (a single Go binary) verified by SHA-256 at install time, and ships the AGPL-3.0 licence text alongside it. Pyroscope runs in single-binary (monolithic) mode, storing all profile data on the VM's own disk, so there is no external object store, no separate database and no extra network port to secure.
Security model. Pyroscope OSS has no authentication of its own, so the entire surface is fronted by nginx, which terminates TLS on port 443 with a certificate generated uniquely for your instance on first boot and guards every route with HTTP Basic Auth using a per-VM password; port 80 redirects to HTTPS. Pyroscope's HTTP server listens on port 4040 — single-binary mode requires it to bind all interfaces because its in-process components discover and dial each other over that port — so isolation is enforced by an in-image ufw host firewall: only SSH (22), HTTPS (443) and the HTTP redirect (80) are accepted from off-box, while loopback is allowed. Because the Linux kernel routes the VM's own traffic to port 4040 through the loopback device, nginx and Pyroscope's internal components reach it while no external host can — even if you open 4040 in your network security group. The only route that answers without authentication is a static /healthz probe.
What is included:
-
Grafana Pyroscope 2.2.1 official release binary at
/usr/local/bin/pyroscope(SHA-256 verified) -
pyroscope.servicesystemd unit running the server in single-binary mode (--target=all) with local filesystem storage under/var/lib/pyroscope/data -
nginxreverse proxy terminating TLS on port 443 (per-VM self-signed certificate) with HTTP Basic Auth in front of every route, and port 80 redirecting to HTTPS -
grafana-pyroscope-firstboot.servicesystemd oneshot that mints the per-VM Basic Auth password and the per-VM TLS certificate on first boot -
A
ufwhost firewall that seals Pyroscope's port 4040 off-box (only 22/80/443 plus loopback are allowed) -
A credential self-test at
/usr/local/sbin/pyroscope-credcheck.sh -
The AGPL-3.0 licence text at
/usr/local/share/pyroscope/LICENSE -
Ubuntu 24.04 LTS base with latest security patches applied at build time
-
24/7 cloudimg support with guaranteed 24 hour response SLA
Prerequisites
-
An active Azure subscription
-
A subscription to the Grafana Pyroscope on Ubuntu 24.04 listing on Azure Marketplace
-
An SSH public key for VM authentication
-
A virtual network and subnet in the target region
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM). Scale up CPU, memory and disk as the number of profiled services and your retention grow.
Step 1: Deploy from the Azure Portal
Navigate to Marketplace in the Azure Portal, search for Grafana Pyroscope, select the cloudimg publisher entry, and click Create.
On the Networking tab attach a network security group that allows inbound TCP 22 from your management IP range, and TCP 443 (and 80 for the HTTP-to-HTTPS redirect) from the networks that will send profiles and use the UI. Every route is protected by the per-VM Basic Auth credential and Pyroscope's own port is firewalled off-box, but you should still restrict source ranges.
Click Review + create, wait for validation, then Create. Deployment takes around two minutes.
Step 2: Deploy from the Azure CLI
RG="pyroscope-prod"
LOCATION="eastus"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" \
--name pyroscope-01 \
--image <publisher>:<offer>:<sku>:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# Allow SSH (22), HTTPS (443) and the HTTP->HTTPS redirect (80) from your trusted ranges
az vm open-port --resource-group "$RG" --name pyroscope-01 --port 22 --priority 1001
az vm open-port --resource-group "$RG" --name pyroscope-01 --port 443 --priority 1002
az vm open-port --resource-group "$RG" --name pyroscope-01 --port 80 --priority 1003
Replace <publisher>:<offer>:<sku> with the URN shown on the Marketplace listing's Usage Information tab.
Step 3: Connect via SSH
ssh azureuser@<vm-public-ip>
Use the private key that matches the public key you supplied at deploy time. The login user for this image is azureuser.
Step 4: Verify the Pyroscope Service
Confirm the Pyroscope server, the nginx proxy and the ufw firewall are running, and check the listening sockets. Pyroscope binds port 4040 on all interfaces (required by single-binary mode) but ufw seals it off-box, so it is reachable only through nginx on port 443. The AGPL-3.0 licence ships with the image.
pyroscope --version 2>/dev/null | head -1
systemctl is-active pyroscope nginx ufw
sudo ss -ltn | grep -E ':443 |:4040 '
head -2 /usr/local/share/pyroscope/LICENSE
Expected output:
pyroscope, version 2.2.1 (branch: HEAD, revision: df3c0391b)
active
active
active
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
LISTEN 0 511 [::]:443 [::]:*
LISTEN 0 4096 *:4040 *:*
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007

Step 5: Retrieve Your Per-VM Credentials
On the first boot of your virtual machine, grafana-pyroscope-firstboot.service generated a Basic Auth password unique to your instance and a per-instance TLS certificate, and wrote the credentials to a root-only file. Retrieve them with:
sudo cat /root/grafana-pyroscope-credentials.txt
The file reports the HTTPS URL and the login:
pyroscope.url=https://<vm-public-ip>/
pyroscope.admin.user=admin
pyroscope.admin.pass=<generated-per-vm>
A shipped self-test confirms the whole credential model without printing any secret — it proves the HTTP-to-HTTPS redirect, the static /healthz probe, that no/bad/default credentials are rejected, and that the per-VM credential is accepted:
sudo bash /usr/local/sbin/pyroscope-credcheck.sh
Expected output:
OK

Step 6: Understand the Access Model
Every Pyroscope route is guarded by the per-VM Basic Auth credential; only the static /healthz probe answers without authentication. There is no default or shared login — no password file ships in the image, so admin/admin is rejected. You can see the gate with curl (the -k flag accepts the per-VM self-signed certificate):
AP=$(sudo grep '^pyroscope.admin.pass=' /root/grafana-pyroscope-credentials.txt | cut -d= -f2-)
curl -ks -o /dev/null -w 'healthz (no auth) -> HTTP %{http_code}\n' https://127.0.0.1/healthz
curl -ks -o /dev/null -w 'UI no auth -> HTTP %{http_code}\n' https://127.0.0.1/
curl -ks -o /dev/null -w 'UI default admin -> HTTP %{http_code}\n' -u 'admin:admin' https://127.0.0.1/
curl -ks -o /dev/null -w 'UI per-VM admin -> HTTP %{http_code}\n' -u "admin:$AP" https://127.0.0.1/
Expected output:
healthz (no auth) -> HTTP 200
UI no auth -> HTTP 401
UI default admin -> HTTP 401
UI per-VM admin -> HTTP 200
You can also confirm the host firewall seals Pyroscope's own port off-box:
sudo ufw status | grep -E 'Status|22|80|443|lo'
Expected output:
Status: active
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
Anywhere on lo ALLOW Anywhere
Port 4040 is deliberately absent from the allow list — it is reachable only via loopback and nginx.

Step 7: Sign In to the Web UI
In a browser, go to https://<vm-public-ip>/. Because the certificate is self-signed and generated per VM, your browser will warn on the first visit — accept it (or install a CA-signed certificate as in Step 11). When prompted for HTTP Basic Auth, enter user admin and the pyroscope.admin.pass from Step 5:

Once authenticated you land on the Pyroscope UI, where you choose a service and a profile type and explore the flamegraph:

Step 8: See the Live Self-Profiling Flamegraph
Because the image runs Pyroscope in single-binary mode, Pyroscope profiles itself and pushes those profiles back to its own store — so real profiling data is present immediately, with no external agent to install. Confirm the profile-type catalogue is populated and render a CPU flamegraph over the HTTP API:
AP=$(sudo grep '^pyroscope.admin.pass=' /root/grafana-pyroscope-credentials.txt | cut -d= -f2-)
# The profile types Pyroscope is collecting about itself
curl -ks -u "admin:$AP" -XPOST https://127.0.0.1/querier.v1.QuerierService/ProfileTypes \
-H 'Content-Type: application/json' -d '{}' | jq -r '.profileTypes[].ID' | head
# Render a CPU flamegraph and summarise it
curl -ks -u "admin:$AP" -G https://127.0.0.1/pyroscope/render \
--data-urlencode 'query=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}' \
--data-urlencode 'from=now-1h' --data-urlencode 'until=now' \
--data-urlencode 'format=json' \
| jq '{numTicks: .flamebearer.numTicks, frames: (.flamebearer.names|length), units: .metadata.units}'
Expected output (values grow as the instance runs):
block:contentions:count:contentions:count
block:delay:nanoseconds:contentions:count
goroutines:goroutine:count:goroutine:count
memory:alloc_objects:count:space:bytes
memory:alloc_space:bytes:space:bytes
memory:inuse_objects:count:space:bytes
memory:inuse_space:bytes:space:bytes
mutex:contentions:count:contentions:count
mutex:delay:nanoseconds:contentions:count
process_cpu:cpu:nanoseconds:cpu:nanoseconds
{
"numTicks": 1350000000,
"frames": 662,
"units": "samples"
}
In the web UI, select the pyroscope service and the process_cpu profile type to explore the same data as an interactive flamegraph:

Step 9: Send Profiles From Your Own Applications
To profile your own services, instrument them with the Grafana profiling SDKs or scrape existing pprof endpoints with Grafana Alloy, pointing them at your instance with the Basic Auth credential. For example, a Go application using the Pyroscope Go SDK:
import "github.com/grafana/pyroscope-go"
pyroscope.Start(pyroscope.Config{
ApplicationName: "my.go.app",
ServerAddress: "https://<vm-public-ip>",
BasicAuthUser: "admin",
BasicAuthPassword: "<pyroscope.admin.pass>",
})
SDKs exist for Go, Java, Python, Ruby, .NET, Node.js and Rust; Alloy's pyroscope.scrape and pyroscope.write components can pull pprof endpoints and forward them here. Each service appears as its own entry in the UI's service selector, alongside the built-in pyroscope self-profile.
Step 10: Change the Basic Auth Password
The password is stored as a bcrypt hash in the nginx htpasswd file. To rotate it, regenerate the entry (this prompts for the new password) and reload nginx:
sudo htpasswd -B /etc/nginx/.pyroscope.htpasswd admin
sudo systemctl reload nginx
htpasswd -B prompts for the new password and writes a fresh bcrypt hash. Update the record in /root/grafana-pyroscope-credentials.txt if you rely on it, or keep your new password in your own secret store.
Step 11: Add a Custom Domain and a CA-Signed Certificate
The image ships a per-VM self-signed certificate so TLS works out of the box. For production, point a DNS name at the VM and install a CA-signed certificate — for example with Let's Encrypt:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d pyroscope.example.com
Certbot obtains a certificate, rewrites the nginx TLS configuration to use it, and sets up automatic renewal. Ensure port 80 is reachable from the internet for the ACME HTTP-01 challenge.
Step 12: Scale Out to Object Storage
Single-binary mode stores profiles on the VM's local disk, which is ideal for a self-contained instance. To retain more data or run a distributed deployment, point Pyroscope at S3-compatible object storage by editing the service's storage flags (for example -storage.backend=s3 with the matching -storage.s3.* settings) in /etc/systemd/system/pyroscope.service, then:
sudo systemctl daemon-reload
sudo systemctl restart pyroscope
See the Pyroscope configuration reference for the full set of object-storage and component options.
Step 13: Uninstall
To remove the VM and its resource group entirely, run this from a workstation with the Azure CLI:
az group delete --name pyroscope-prod --yes --no-wait
Support
cloudimg provides 24/7 technical support for this Grafana Pyroscope product by email (support@cloudimg.co.uk) and live chat, with a guaranteed 24 hour response SLA. We help with deployment and initial configuration, retrieving and rotating the Basic Auth password, adding a custom domain and CA-signed certificate, instrumenting your applications with the Grafana profiling SDKs and Alloy, reading and interpreting flamegraphs, scaling out to object storage, retention and performance tuning, upgrades and patch management, and troubleshooting.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.