Observability AWS

Apache SkyWalking 11.1.0 on AWS User Guide

| Product: Apache SkyWalking 11.1.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Apache SkyWalking 11.1.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg tracing and APM listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Apache SkyWalking is an open source application performance monitoring (APM) system, a top-level project of the Apache Software Foundation. Language agents (Java, Python, Go, Node.js, .NET, PHP and others) and OpenTelemetry exporters report traces, metrics and logs to the SkyWalking OAP server, which draws the service topology, computes throughput, latency, success rate and Apdex for every service and endpoint, and keeps distributed traces you can open span by span.

What the cloudimg image adds:

  • No default login, for people and for agents. The Horizon web UI signs in with the user admin and a password generated on your instance's first boot; only its argon2id hash is stored. Agents report with an agent token that is also generated on first boot. Both are written to /var/lib/cloudimg/skywalking-credentials.txt (root only), and the image ships no password, no token and no TLS key.
  • One port for everything, behind the token. nginx on port 443 serves the Horizon UI and accepts agent and OpenTelemetry (OTLP/gRPC) reports over gRPC with TLS. A report without this instance's agent token is refused with HTTP 401 before it reaches SkyWalking, for every forwarded service, and the OAP server checks the token again as a second layer. Only the receiver services are forwarded: the OAP's internal cluster and query interfaces are never reachable from outside.
  • The OAP server and storage on localhost only. The OAP server (11800 gRPC, 12800 REST, 3200 TraceQL, 17128-17129 admin), BanyanDB and the Horizon server listen on 127.0.0.1, and a loopback guard lets only the accounts that need each port (plus root and administrators) connect.
  • HTTPS from the first boot. The certificate is generated on your instance for its own names and addresses, port 80 redirects to HTTPS, and requests for a host name the instance does not serve are dropped. Cross-site and cross-origin requests to the UI are refused and sign-in attempts are rate limited.
  • No metadata access and no surprise egress. The OAP server, BanyanDB, Horizon and nginx are blocked from the instance metadata service, the AMI requires IMDSv2, and the SkyWalking accounts cannot open connections to private or link-local networks unless you allow a range (Step 10).
  • Fail closed. The services start only after first boot has finished, and a pre-start check refuses to start a service whose configuration has drifted from the secure settings, rather than starting it exposed.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on. Every component download is checked against a pinned SHA-512 or SHA-256 checksum, recorded in the build record.

What is on the image: Apache SkyWalking 11.1.0 (the OAP server) on OpenJDK 21 from Ubuntu, Apache SkyWalking BanyanDB 0.11.2 (standalone) as the storage, the Apache SkyWalking Horizon UI 1.1.0 on Node.js 24, nginx 1.24 and OpenSSL 3.0 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large (t3.large also fits: both have 8 GiB of memory)

The Horizon Services Dashboard with the traced services, a virtual PostgreSQL database and the service topology from frontend through shop-api to payments

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows:
  • TCP 22 (SSH) from your admin network only;
  • TCP 443 (the Horizon UI and agent ingest) from the people who use the UI and from the applications, agents and OpenTelemetry Collectors that report data. Agents in the same VPC can use the instance's private address;
  • TCP 80 only if you want plain HTTP requests redirected to HTTPS. You can leave it closed.
  • Never open 11800, 12800, 3200, 17128, 17129, 8081 or the BanyanDB ports (17912, 17913, 17916, 2121, 6060). They listen on the loopback address only, and agents do not need them: on this image the agents report to port 443.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Apache SkyWalking 11.1.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (or another type with at least 8 GiB of memory), your key pair and a security group as above. BanyanDB keeps traces and metrics on the root volume under /var/lib/banyandb, so size it for your traffic and retention (Step 9); 30 GiB is a sensible start.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=skywalking}]'

First boot takes a few seconds once the instance is running. The OAP server then needs two to three minutes on its first start to create its storage schema in BanyanDB; the UI signs you in meanwhile and shows data once it is ready.

Step 2: Connect to your instance

Version SSH user
Apache SkyWalking 11.1.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the password, the agent token and the certificate, then BanyanDB, the OAP server, Horizon and nginx start:

systemctl is-active banyandb skywalking-oap skywalking-horizon nginx skywalking-firstboot
sudo test -f /var/lib/cloudimg/skywalking-firstboot.done && echo FIRSTBOOT_DONE

All five units are active and the first boot marker is present:

active
active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on network ports; the OAP server (java), BanyanDB (banyand) and Horizon (MainThread, the Node.js server) are on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v ':53 ' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:11800 users:(("java"))
127.0.0.1:12800 users:(("java"))
127.0.0.1:17128 users:(("java"))
127.0.0.1:17129 users:(("java"))
127.0.0.1:17912 users:(("banyand"))
127.0.0.1:17913 users:(("banyand"))
127.0.0.1:17916 users:(("banyand"))
127.0.0.1:2121 users:(("banyand"))
127.0.0.1:3200 users:(("java"))
127.0.0.1:6060 users:(("banyand"))
127.0.0.1:8081 users:(("MainThread"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

The health check nginx serves without a login, then the OAP server's own health check on the loopback address (reachable from your administrator shell):

curl -sk https://127.0.0.1/healthz
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:12800/healthcheck
ok
200

Step 4: Get your URL, password and agent token

sudo cat /var/lib/cloudimg/skywalking-credentials.txt

The file is readable by root only:

# Apache SkyWalking 11.1.0 (OAP server, BanyanDB 0.11.2 storage, Horizon UI 1.1.0) - generated on first boot by
# skywalking-firstboot.service. The password and the agent token are unique to this instance; neither was baked into
# the image. Keep this file secret.

HORIZON_URL=https://<public-dns-name>/
HORIZON_ADMIN_USER=admin
HORIZON_ADMIN_PASSWORD=<HORIZON_ADMIN_PASSWORD>
#
# SkyWalking agents and OpenTelemetry (OTLP/gRPC) exporters report over gRPC + TLS on port 443 with this token as the
# 'Authentication' gRPC metadata. The certificate is self-signed: give agents /etc/ssl/skywalking/skywalking.crt as
# their CA (Java agent: agent/ca/ca.crt plus agent.force_tls=true).
AGENT_COLLECTOR_BACKEND_SERVICE=<public-dns-name>:443
AGENT_AUTHENTICATION=<AGENT_AUTHENTICATION>
AGENT_CA_CERTIFICATE=/etc/ssl/skywalking/skywalking.crt
#
# New admin password: sudo skywalking-admin-password   New agent token: sudo skywalking-agent-token
# Self-test: sudo skywalking-selftest   Change the served name: sudo skywalking-set-hostname <your-domain>
# User guide: https://www.cloudimg.co.uk/guides/apache-skywalking-11-1-0-on-ubuntu-24-04-aws/

The UI is served as the instance's public DNS name from the EC2 instance metadata; the certificate also names the public and private IP addresses and the private DNS name, and requests to the IP addresses redirect to the name. If you stop and start the instance without an Elastic IP, the boot service picks up the new name and issues a new certificate. To serve SkyWalking under your own DNS name, see Step 11.

Open the URL in your browser. It warns once about the self-signed certificate; accept it (or install your own certificate, Step 11), then sign in as admin with the password from the credentials file:

The Horizon sign-in page with the username and password fields

Step 5: See it work with example data

Before you connect your own applications, you can load an example: one minute of checkout requests through three services (frontend, shop-api and payments) that call a PostgreSQL database and a card network, with every twelfth request failing. The block builds SkyWalking trace segments, the format the language agents send, and posts them to the OAP server's HTTP receiver on the loopback address (from the instance itself; remote agents use port 443 with the token, Step 6):

python3 - > /tmp/skywalking-demo.json <<'PY'
import json, secrets, time
now = int(time.time() * 1000)
def span(sid, parent, op, kind, layer, comp, t0, t1, peer="", err=False, tags=()):
    s = {"spanId": sid, "parentSpanId": parent, "startTime": t0, "endTime": t1, "operationName": op,
         "spanType": kind, "spanLayer": layer, "componentId": comp, "isError": err,
         "tags": [{"key": k, "value": v} for k, v in tags]}
    if peer:
        s["peer"] = peer
    return s
def ref(tid, seg, svc, inst, ep, peer):
    return [{"refType": "CrossProcess", "traceId": tid, "parentTraceSegmentId": seg, "parentSpanId": 1,
             "parentService": svc, "parentServiceInstance": inst, "parentEndpoint": ep,
             "networkAddressUsedAtPeer": peer}]
segments = []
for i in range(60):
    t = now - (60 - i) * 10000
    tid, s1, s2, s3 = (secrets.token_hex(16) for _ in range(4))
    fail = i % 12 == 5
    segments.append({"traceId": tid, "traceSegmentId": s1, "service": "frontend", "serviceInstance": "frontend-1",
        "spans": [span(1, 0, "POST /api/orders", "Exit", "Http", 2, t + 20, t + 400, peer="shop-api:8080"),
                  span(0, -1, "GET /checkout", "Entry", "Http", 1, t, t + 420, err=fail,
                       tags=[("http.method", "GET"), ("http.status_code", "500" if fail else "200")])]})
    s2spans = [span(1, 0, "POST /charge", "Exit", "Http", 2, t + 170, t + 380, peer="payments:8080"),
               span(2, 0, "SELECT orders", "Exit", "Database", 22, t + 40, t + 160, peer="postgres:5432",
                    tags=[("db.type", "sql"), ("db.statement", "SELECT * FROM orders WHERE id = ?")]),
               span(0, -1, "POST /api/orders", "Entry", "Http", 1, t + 30, t + 390, err=fail,
                    tags=[("http.method", "POST")])]
    segments.append({"traceId": tid, "traceSegmentId": s2, "service": "shop-api", "serviceInstance": "shop-api-1",
        "spans": [dict(s, refs=ref(tid, s1, "frontend", "frontend-1", "GET /checkout", "shop-api:8080"))
                  if s["spanId"] == 0 else s for s in s2spans]})
    s3spans = [span(1, 0, "card-network authorize", "Exit", "Http", 2, t + 200, t + 360, peer="card-network:443",
                    err=fail),
               span(0, -1, "POST /charge", "Entry", "Http", 1, t + 180, t + 375, err=fail,
                    tags=[("http.method", "POST")])]
    segments.append({"traceId": tid, "traceSegmentId": s3, "service": "payments", "serviceInstance": "payments-1",
        "spans": [dict(s, refs=ref(tid, s2, "shop-api", "shop-api-1", "POST /api/orders", "payments:8080"))
                  if s["spanId"] == 0 else s for s in s3spans]})
print(json.dumps(segments))
PY
sudo curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' \
  --data-binary @/tmp/skywalking-demo.json http://127.0.0.1:12800/v3/segments

The OAP server accepts the 180 segments:

HTTP 200

The OAP server aggregates them within a minute. Ask its GraphQL query API (the API the UI uses) which services it found, the topology it drew and how many failed traces it kept:

sleep 45
gql() { sudo curl -s -X POST -H 'Content-Type: application/json' http://127.0.0.1:12800/graphql --data-binary "$1"; }
gql '{"query":"{ listServices(layer: \"GENERAL\") { name } }"}' \
  | jq -c '[.data.listServices[].name | select(startswith("cloudimg-selftest") | not)] | sort'
D="{start: \"$(date -u -d '-30 min' '+%Y-%m-%d %H%M')\", end: \"$(date -u '+%Y-%m-%d %H%M')\", step: MINUTE}"
gql "{\"query\":\"{ getGlobalTopology(duration: ${D//\"/\\\"}) { nodes { id name } calls { source target } } }\"}" \
  | jq -c '.data.getGlobalTopology as $t | ($t.nodes | map({(.id): .name}) | add) as $n | [$t.calls[] | "\($n[.source]) -> \($n[.target])"] | map(select(test("cloudimg-selftest") | not)) | sort'
gql "{\"query\":\"{ queryTraces(condition: {queryDuration: ${D//\"/\\\"}, traceState: ERROR, queryOrder: BY_START_TIME, paging: {pageNum: 1, pageSize: 20}}) { traces { spans { serviceCode } } } }\"}" \
  | jq '.data.queryTraces.traces | length'
["frontend","payments","shop-api"]
["User -> frontend","frontend -> shop-api","payments -> card-network:443","shop-api -> payments","shop-api -> postgres:5432"]
5

In the UI, the Services Dashboard (the screenshot at the top of this guide) shows the three services (plus a cloudimg-selftest-<id> service for each self-test run, Step 13), the database SkyWalking recognised from the agents' exit spans (postgres:5432, under Virtual Database) and the topology with calls per minute. General Service opens the metrics of each service: throughput, error rate, Apdex, response time percentiles and the busiest endpoints and instances:

The General Service page with payments selected in the service list and its traffic, error rate and Apdex charts

Under General Service, Traces lists the traces (choose Run query); select one to open its timeline. Each row is a span, indented under its parent, from frontend through shop-api to payments:

A GET /checkout trace with seven spans across frontend, shop-api and payments over 420 ms

The example data ages out with the retention settings (3 days for traces, Step 9).

Step 6: Connect your applications

Agents and OpenTelemetry exporters report to <public-dns-name>:443 (or the private IP address or your own DNS name, which the certificate names) over gRPC with TLS, and send the agent token as the Authentication gRPC metadata. The OAP server's own gRPC port, 11800, stays on the loopback address: nginx forwards only the receiver services and only with the token. Copy the instance's certificate, /etc/ssl/skywalking/skywalking.crt, to the agents so they can verify it, or install a certificate your systems already trust (Step 11).

SkyWalking Java agent. Download the agent from the SkyWalking downloads page, copy the certificate to skywalking-agent/ca/ca.crt and set (in skywalking-agent/config/agent.config or as environment variables):

SW_AGENT_NAME=my-service
SW_AGENT_COLLECTOR_BACKEND_SERVICES=<public-dns-name>:443
SW_AGENT_AUTHENTICATION=<AGENT_AUTHENTICATION>
SW_AGENT_FORCE_TLS=true

Then start your application with java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar -jar my-app.jar. The Python, Go, Node.js, .NET and PHP agents take the same three settings (the backend address, the authentication token and TLS); see each agent's documentation on the SkyWalking site.

OpenTelemetry Collector. OTLP/gRPC traces, metrics and logs are accepted on the same port with the same token. Use the otlp exporter:

exporters:
  otlp/skywalking:
    endpoint: <public-dns-name>:443
    headers:
      Authentication: ${env:SKYWALKING_AGENT_TOKEN}
    tls:
      ca_file: /etc/otelcol/skywalking.crt
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlp/skywalking]

SkyWalking's native agents give the full experience (topology, service and endpoint metrics, traces); what the OpenTelemetry receiver builds from each OTLP signal is described in the SkyWalking OpenTelemetry receiver documentation.

nginx refuses a report without the token, or with a wrong one, with HTTP 401 (gRPC clients report it as UNAUTHENTICATED), and the OAP server's internal gRPC services are not reachable at all:

curl -sk --http2 -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/grpc' https://127.0.0.1/skywalking.v3.ManagementService/keepAlive
curl -sk --http2 -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/grpc' -H 'Authentication: wrong-token' https://127.0.0.1/opentelemetry.proto.collector.trace.v1.TraceService/Export
curl -sk --http2 -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-Type: application/grpc' https://127.0.0.1/skywalking.v3.RemoteService/call
401
401
401

Keep the token in a secrets store or a file only the agent can read, never in source code or an image you publish.

Step 7: Rotate the agent token

To issue a new agent token (for example when someone who knew it leaves), run:

sudo skywalking-agent-token

It writes the new token to the credentials file, nginx's ingest check and the OAP server's configuration, restarts the OAP server and reloads nginx. From then on every agent still using the old token is refused, so update SW_AGENT_AUTHENTICATION (or the collector's header) on every sender:

skywalking-agent-token: a new agent token is in /var/lib/cloudimg/skywalking-credentials.txt (AGENT_AUTHENTICATION)

Step 8: Java heap and instance size

The OAP server runs on Java 21 with a 1 GiB initial and 2 GiB maximum heap, sized for an 8 GiB m5.large or t3.large next to BanyanDB and Horizon:

systemctl show -p ExecStart skywalking-oap | grep -oE -- '-Xm[sx][0-9]+[gm]'
-Xms1g
-Xmx2g

On a larger instance (for example 16 GiB), give the OAP server more heap with a systemd drop-in that copies the start command with new values. This one sets 2 GiB initial and 4 GiB maximum; the change lasts across reboots and the image's own drop-in (50-cloudimg-imds-deny.conf) stays in place:

sudo sed -n 's/^ExecStart=//p' /etc/systemd/system/skywalking-oap.service | sed 's/ -Xms1g -Xmx2g / -Xms2g -Xmx4g /' \
  | { printf '[Service]\nExecStart=\nExecStart=%s\n' "$(cat)"; } \
  | sudo tee /etc/systemd/system/skywalking-oap.service.d/90-local.conf >/dev/null
sudo systemctl daemon-reload && sudo systemctl restart skywalking-oap

Keep the maximum heap at about half of the instance's memory: BanyanDB uses the operating system's page cache for its data. A burstable instance (t3) that runs out of CPU credits slows the OAP server's aggregation; use an m5 or unlimited mode for steady traffic.

Step 9: Storage and retention

BanyanDB stores everything on the local disk under /var/lib/banyandb, with no separate database to run. The OAP server reports the retention it applies, in days:

curl -s http://127.0.0.1:12800/status/config/ttl | grep -E '^(metrics\.(minute|hour|day)|records\.(normal|trace|otlpTrace|log))='
metrics.minute=7
metrics.hour=15
metrics.day=15
records.normal=3
records.trace=3
records.otlpTrace=3
records.log=3

Each value is set by an environment variable in /etc/skywalking/bydb.yml (for example SW_STORAGE_BANYANDB_TRACE_TTL_DAYS for traces and SW_STORAGE_BANYANDB_METRICS_MINUTE_TTL_DAYS for minute metrics). To keep traces for 7 days, add an Environment= line to the OAP server's drop-in (create /etc/systemd/system/skywalking-oap.service.d/90-local.conf with a [Service] line if Step 8 did not), then restart it:

sudo sed -i '/^\[Service\]/a Environment=SW_STORAGE_BANYANDB_TRACE_TTL_DAYS=7' /etc/systemd/system/skywalking-oap.service.d/90-local.conf
sudo systemctl daemon-reload && sudo systemctl restart skywalking-oap

Data retention in the UI shows the same values. Plan the root volume for your traffic and retention, and watch it with df -h /.

Step 10: Let SkyWalking reach a private address (alarm webhooks)

The skywalking, banyandb and horizon accounts cannot open connections to the instance metadata service, link-local addresses or any private network, including your VPC. If you configure an alarm webhook (/etc/skywalking/alarm-settings.yml) that points to a service on a private address, allow that range first, one CIDR per line:

echo '10.0.12.0/24' | sudo tee -a /etc/cloudimg/skywalking-egress-allow
sudo systemctl reload cloudimg-skywalking-egress

The file accepts only private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 and fc00::/7); metadata and link-local addresses can never be allowed. Public webhook URLs (for example Slack) need no change.

Step 11: Your own DNS name and certificate

Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name SkyWalking is served as. This mints a new self-signed certificate for the name, updates nginx, the cross-site and cross-origin guards, Horizon's public URL and the credentials file, and keeps the name across reboots:

sudo skywalking-set-hostname <your-domain>

Give agents the new /etc/ssl/skywalking/skywalking.crt (or update their backend address if they used the old name). To use a certificate from a public or internal certificate authority instead, copy it over the generated pair and reload nginx (running skywalking-set-hostname again replaces it with a self-signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/skywalking/skywalking.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/skywalking/skywalking.key
sudo nginx -t && sudo systemctl reload nginx

Step 12: Change the administrator password

sudo skywalking-admin-password

This generates a new 24-character password for admin, stores only its argon2id hash in Horizon's configuration, writes the password to the credentials file and restarts Horizon, which signs out every session. Read the new password from the credentials file (Step 4). To give each person their own login, use Users in the UI.

Step 13: Security checks

The shipped self-test signs in to Horizon through nginx (and proves a wrong password is refused), checks that the ingest refuses a missing or wrong agent token and accepts this instance's token, reports a test trace segment and reads it back through Horizon, and checks the cross-site, cross-origin and unknown-host guards, that every SkyWalking service listens on the loopback address only and that the instance metadata service cannot be reached from inside any of the services. Secrets reach curl only through root-only temporary files, never a command line:

sudo skywalking-selftest
SELFTEST-OK https 301 + healthz + Horizon page + /api/auth/me 401 anonymous, login 401 wrong / 200 per-instance admin + gRPC ingest 401 without or with a wrong token, 0 with the agent token, RemoteService 404, browser 403 + segment <id> reported and read back through Horizon + 403 cross-origin login + 403 cross-site API + 444 unknown host + loopback-only banyandb/oap/horizon + metadata 000 inside 4 services (control 200)

Each self-test run adds a small cloudimg-selftest-<id> service to the UI, which ages out with the retention settings.

The metadata block is a systemd setting on each service:

systemctl show -p IPAddressDeny skywalking-oap banyandb skywalking-horizon nginx
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

A request for a host name the instance does not serve gets no answer at all (nginx closes the connection, so curl exits with code 52):

curl -sk --http1.1 -o /dev/null -w '%{http_code}\n' -H 'Host: attacker.example' https://127.0.0.1/healthz; echo "curl exit $?"
000
curl exit 52

The certificate was generated on this instance for its own names and addresses:

sudo openssl x509 -in /etc/ssl/skywalking/skywalking.crt -noout -subject -ext subjectAltName
subject=CN = <public-dns-name>, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:<public-dns-name>, IP Address:<public-ip>, IP Address:<private-ip>, DNS:<private-dns-name>, IP Address:127.0.0.1, DNS:localhost

Step 14: Versions, build record and files

grep -E '^(SkyWalking OAP|BanyanDB|Horizon UI|Node.js|Java|OAP heap) ' /usr/share/doc/cloudimg-skywalking/BUILD-INFO.txt | cut -c1-60
nginx -v 2>&1
SkyWalking OAP    11.1.0, Apache-2.0; apache-skywalking-apm-
BanyanDB          0.11.2 (standalone, banyand-server-slim-li
Horizon UI        1.1.0, Apache-2.0; apache-skywalking-horiz
Node.js           24.22.2 (bin/node only), MIT; node-v24.22.
Java              openjdk version "21.0.12.1" 2026-08-18 (op
OAP heap          -Xms1g -Xmx2g (skywalking-oap.service)
nginx version: nginx/1.24.0 (Ubuntu)
Path What it is
/opt/skywalking The OAP server, run by skywalking-oap.service as the skywalking user
/etc/skywalking/application.yml The OAP server's settings: receivers, the agent token check, the loopback addresses
/etc/skywalking/bydb.yml BanyanDB storage groups and retention
/opt/banyandb, /var/lib/banyandb BanyanDB (banyandb.service) and its data
/opt/skywalking-horizon, /etc/horizon/horizon.yaml The Horizon UI (skywalking-horizon.service) and its settings, with the administrator's password hash
/etc/nginx/sites-available/cloudimg-skywalking, /etc/nginx/skywalking-ingest.conf nginx: HTTPS, the UI proxy and the agent ingest with the token check
/etc/ssl/skywalking/ This instance's certificate and private key
/var/lib/cloudimg/skywalking-credentials.txt URL, administrator password and agent token (root only)
/etc/cloudimg/skywalking-egress-allow Private ranges the SkyWalking services may reach (Step 10)
/usr/share/doc/cloudimg-skywalking/ The licences and notices, the build record with every checksum and the source offer

Step 15: Security recommendations

  • Keep port 443 open only to the people who use the UI and the networks your agents report from, and port 22 to your admin network. Never open the loopback-only ports listed in the prerequisites.
  • Use your own DNS name and a certificate from a certificate authority your agents trust (Step 11).
  • Keep the password and the agent token in a secrets store; change the password (Step 12) and rotate the token (Step 7) when people leave. Traces can carry URLs, user identifiers and SQL statements, so treat them as sensitive data and use the agents' options to leave out or mask what you do not need.
  • The instance applies Ubuntu security updates automatically. To update SkyWalking, BanyanDB or Horizon, read their release notes, back up /var/lib/banyandb (stop the services first, or snapshot the volume) and follow the upgrade documentation; or launch a newer version from the listing and point your agents at it.

Support and licensing

Apache SkyWalking, Apache SkyWalking BanyanDB and the Apache SkyWalking Horizon UI are open source software licensed under the Apache License 2.0; this image ships the unmodified upstream releases (downloads), verified against their published SHA-512 checksums. The licence and notice files of every component, including the licences of the libraries the OAP server bundles, are in /usr/share/doc/cloudimg-skywalking, with a source offer. OpenJDK is licensed under the GNU GPL version 2 with the Classpath Exception, Node.js under the MIT licence and nginx under the 2-clause BSD licence. cloudimg provides 24/7 support for this image by email at support@cloudimg.co.uk and through the cloudimg website: we help with launching, connecting agents and collectors, reading traces and metrics, alarms, storage and retention, TLS with your own domain, upgrades and troubleshooting.

Apache, Apache SkyWalking, SkyWalking and the Apache feather logo are trademarks or registered trademarks of the Apache Software Foundation. OpenTelemetry is a trademark of The Linux Foundation. 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.