Wakapi
Overview
Wakapi is an open source, self-hosted coding statistics server that speaks the WakaTime API. You keep using the standard WakaTime plugins for Visual Studio Code, JetBrains IDEs, Vim, Neovim, Sublime Text and dozens of other editors. You only point them at your own server.
Wakapi turns the "heartbeats" those plugins send into dashboards of the time you spend per project, language, editor, operating system and machine. It also provides daily and ranged summaries, project pages, badges and a WakaTime-compatible REST API. Your coding history stays inside your own Azure subscription.
This guide covers deploying and using Wakapi 2.18.2 on Ubuntu 24.04 LTS from SecureAnyCloud on Microsoft Azure.
What you get:
- HTTPS from the first boot. nginx serves Wakapi on port 443 with a certificate generated on your VM's first boot. Port 80 redirects to HTTPS, and Wakapi's session cookies are marked Secure.
- An administrator made for your VM, nobody else. The first boot creates an
adminadministrator with a password and an API key generated on your VM, before nginx or Wakapi accept a single connection. It writes them to/var/lib/sac/data/wakapi-credentials.txt(root only). It also generates the VM's password salt and the key that signs its session cookies. No account, password or key is baked into the image. - Closed to strangers. Public sign-up and invite codes are off, so nobody can register. Visitors who are not signed in land on the login page, and the public leaderboard is off.
- Your plugins keep working. WakaTime plugins authenticate with your API key on
/api, so there is no extra password prompt in front of the API. A heartbeat with a wrong key is refused. - Outbound requests limited to WakaTime. Wakapi can forward your heartbeats to a WakaTime account. On this image Wakapi can reach
api.wakatime.com(andwakapi.dev) and nothing else, so a user cannot make the server fetch an address of their choosing. Data imports are off until you enable them (Step 8). - No route to the Azure metadata service. Wakapi, nginx and the first-boot service are blocked at the kernel level from the Azure Instance Metadata Service and the WireServer, even if you later allow more outbound hosts. The Azure VM agent keeps its access.
- Cross-site requests refused. nginx refuses a form post from another website. It also refuses a heartbeat from another site that relies on your browser's session instead of an API key, and another site's attempt to trigger the links that change a setting. Compressed request bodies are refused.
- Features you do not use are closed. The built-in API explorer (
/swagger-ui, which bundles an old copy of the DOMPurify library), OpenID Connect sign-in, the anonymous plugin error report, metrics and subscriptions answer 404. - The host name comes from configuration, never from the request. Wakapi's public URL is set on first boot from the VM's public IP address (read from the Azure Instance Metadata Service). nginx refuses requests for host names the VM does not serve. Wakapi always sees the real client address, whatever forwarding headers a request carries, so its login rate limit applies to the real client.
- A current, checked build. Wakapi 2.18.2 includes the fix for GHSA-x48w-3rq3-w2pq (a pre-authentication account takeover fixed in 2.17.6). The upstream release binary was built with an older Go and network library that govulncheck flags. SecureAnyCloud therefore builds the 2.18.2 source tag with Go 1.27.3 and the fixed golang.org/x/net, reproducibly, and govulncheck finds no vulnerable code in it.
- Hardened defaults. Wakapi runs as its own unprivileged user with no capabilities and a read-only view of the system except its data directory. nginx and Wakapi refuse to start until first boot has finished.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Wakapi 2.18.2 (/usr/local/bin/wakapi, with its web UI built in, listening on 127.0.0.1:3000 only), nginx, SQLite, Ubuntu 24.04 LTS, cloud-init and the Azure Linux agent.
- Default user: the admin user you choose (
azureuserin the examples) - Architecture: x64, Gen2
- Recommended size:
Standard_B2s
Prerequisites
- An Azure subscription, an SSH public key, and a virtual network and subnet in your region.
- A network security group that allows TCP 22 (SSH), TCP 443 (Wakapi) and TCP 80 (redirects to HTTPS) from your own networks only. Include the networks your developers' editors send heartbeats from.
Deploy from the Azure portal
Search the Azure Marketplace for Wakapi, choose the SecureAnyCloud offer and select Create.
Pick your subscription, resource group and region. Choose size Standard_B2s, because Wakapi is light and a few developers need very little. Add an SSH public key for the admin user, and allow inbound ports 22, 80 and 443. Then select Review + create.
Deploy from the Azure CLI
RG="wakapi-rg"; LOCATION="eastus"; VM_NAME="wakapi1" IMAGE_URN="sac1647283583153:wakapi-ubuntu-24-04:default:latest" az group create --name "$RG" --location "$LOCATION" az vm image terms accept --urn "$IMAGE_URN" az vm create --resource-group "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
--size Standard_B2s --admin-username azureuser --ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 443 --priority 1001 az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1002az vm create opens port 22 for you. If you can, narrow the rules to your own networks with az network nsg rule update --source-address-prefixes <your-cidr>.
Connect and check the services
ssh azureuser@<public-ip>First boot reads the VM's public IP from the Azure Instance Metadata Service. It generates the certificate, the password salt, the cookie key and the administrator, and then Wakapi and nginx start:
systemctl is-active wakapi nginx wakapi-firstboot wakapi-address
sudo test -f /var/lib/sac/data/wakapi-firstboot.done && echo FIRSTBOOT_DONEAll four units are active and the first-boot marker is present:
active active active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network. Wakapi itself listens on 127.0.0.1 only. Check the listeners:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort0.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:3000 users:(("wakapi")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"))Get your URL, password and API key
wakapi-address.service reads the VM's addresses from the Azure Instance Metadata Service on every boot. For a Standard SKU public IP, it uses the load balancer view, which is the only view that shows it. It is the one service of the image that may reach the metadata service, and it runs no Wakapi code:
cat /run/sac-wakapi/address.envWAKAPI_PUBLIC_IP=<public-ip> WAKAPI_PRIVATE_IPS="<private-ip>"sudo cat /var/lib/sac/data/wakapi-credentials.txtOnly root can read the file. It holds the URL, the administrator's user name, this VM's password and the administrator's API key:
# Wakapi 2.18.2 - generated on first boot by wakapi-firstboot.service
# This administrator was created on THIS VM; no account was baked into the image. Keep this file secret and
# change the password after your first sign-in (Settings > Account). WAKAPI_URL=https://<public-ip>/ WAKAPI_ADMIN_USER=admin WAKAPI_ADMIN_PASSWORD=<WAKAPI_ADMIN_PASSWORD> WAKAPI_API_KEY=<WAKAPI_API_KEY>
#
# Open WAKAPI_URL in a browser (self-signed certificate minted on this VM's first boot; port 80 redirects to # https) and sign in. Point your editor's WakaTime plugin at this server in ~/.wakatime.cfg:
# [settings]
# api_url = https://<public-ip>/api
# api_key = <WAKAPI_API_KEY>
# (with this image's self-signed certificate also add: no_ssl_verify = true, or install a CA-issued certificate) # Public sign-up and invite codes are OFF. Forwarding to WakaTime reaches api.wakatime.com only; imports are off. # Self-test:
sudo wakapi-selftest
# Change the name it is served as:
sudo wakapi-set-hostname <your-dns-name>
# User guide: https://secureanycloud.com/stacks/wakapi/The URL uses the VM's public IP address. The certificate also names the private address, and requests for the private address are answered too. To serve Wakapi under your own DNS name, see Step 10.
Sign in
Open the WAKAPI_URL in your browser. The certificate is self-signed. It is generated on this VM and is a server certificate that cannot sign other certificates, so your browser warns about it the first time. Before you accept it, compare the fingerprint your browser shows with the one the VM prints:
sudo openssl x509 -in /etc/ssl/wakapi/wakapi.crt -noout -fingerprint -sha256sha256 Fingerprint=<64 hex digits in pairs, separated by colons>Accept the certificate only if the two match. Do not add it to your operating system's or browser's trust store. For a certificate your users' machines trust without a warning, use a CA-issued certificate for your own DNS name instead (Step 10).
Sign in as admin with the password from Step 4. The Sign up button is still drawn on the page, but registration is switched off. Wakapi answers "registration is disabled on this server".
Then open Settings, and change the password under Account to one only you know.
Connect your editors
Install the WakaTime plugin for your editor. The WakaTime plugin list covers VS Code, JetBrains IDEs, Vim, Neovim, Sublime Text, Xcode and many more. Then point the plugin at your Wakapi server instead of WakaTime. On each workstation, edit ~/.wakatime.cfg (on Windows %USERPROFILE%\.wakatime.cfg):
[settings] api_url = https://<your-vm-address>/api api_key = <WAKAPI_API_KEY> no_ssl_verify = trueYou need no_ssl_verify only while the VM uses its self-signed certificate. Remove it once you install a CA-issued certificate (Step 10). Wakapi's Setup page shows the same snippet for the account you are signed in as.
You can check the API from the VM itself. This sends one test heartbeat with the administrator's API key and reads it back. The key is read from the credentials file into a temporary file and is never typed on a command line:
H=$(sudo sed -n 's#^WAKAPI_URL=https://\([^/]*\)/#\1#p' /var/lib/sac/data/wakapi-credentials.txt)
CFG=$(mktemp); chmod 600 "$CFG" printf 'header = "Authorization: Basic %s"\n' "$(sudo sed -n 's/^WAKAPI_API_KEY=//p' /var/lib/sac/data/wakapi-credentials.txt | tr -d '\n' | base64 -w0)" > "$CFG" curl -sk -K "$CFG" --resolve "$H:443:127.0.0.1" -o /dev/null -w 'heartbeat: %{http_code}\n' \
-H 'Content-Type: application/json' -A 'wakatime/v1.130.1 (linux) curl-test' \
--data "[{\"entity\":\"/home/dev/hello/main.go\",\"type\":\"file\",\"time\":$(date +%s),\"project\":\"hello-wakapi\",\"language\":\"Go\",\"category\":\"coding\"}]" \
"https://$H/api/heartbeat"
TODAY=$(curl -sk -K "$CFG" --resolve "$H:443:127.0.0.1" "https://$H/api/compat/wakatime/v1/users/current/heartbeats?date=$(date -u +%F)") rm -f "$CFG" grep -o '"project":"hello-wakapi"' <<<"$TODAY" | sort -uheartbeat: 201 "project":"hello-wakapi"Each person who uses the server should have their own account and API key (Step 8). You can list, add and revoke API keys under Settings > API Keys.
Your dashboard
Once your editors send heartbeats, Dashboard shows the totals and breakdowns for the interval you pick (today, this week, the past 7 days, a custom range and more). It breaks them down by project, language, editor, operating system, machine, label and category. The filters at the top narrow everything to one project, language or machine.
Projects lists every project you worked on, most recent first. Choose one to see its own statistics.
Users, forwarding to WakaTime and imports
The image ships as single-user: sign-up, invite codes and imports are off. The settings live in /etc/wakapi/config.yml:
sudo grep -E '^ (public_url|allow_signup|invite_codes|import_enabled|trust_reverse_proxy_ips|insecure_cookies|disable_frontpage):' /etc/wakapi/config.ymlpublic_url: https://<public-ip> import_enabled: false insecure_cookies: false allow_signup: false invite_codes: false disable_frontpage: true trust_reverse_proxy_ips: 127.0.0.1- Add teammates with invite codes. Set
invite_codes: trueand restart Wakapi (sudo systemctl restart wakapi). Settings then shows an Invite Friends button that generates a single-use invite link, valid for 24 hours. Send the link to a colleague, who registers with it while open sign-up stays off. Avoidallow_signup: trueunless the VM is reachable only from a private network. - Forward your heartbeats to WakaTime. Under Settings > Integrations, enter your WakaTime API key and choose Connect. Wakapi then relays each heartbeat to
api.wakatime.comas well, so you can use both. - Forward to another server, or import data. Wakapi's outbound requests are limited to the hosts in
NO_PROXYin/etc/wakapi/egress.env. To forward to your own Wakapi or WakaTime-compatible server, or to import history from WakaTime (whose data export downloads from a separate storage host), add the host names, comma-separated, toNO_PROXY. For imports, also setimport_enabled: trueand add the host toimport_hosts_whitelistinconfig.yml. Then runsudo systemctl restart wakapi. Even then, Wakapi cannot reach the Azure metadata service or the WireServer. That block is in the kernel, not in the allowlist.
Run the self test
The image has a self test. It checks HTTPS, that anonymous visitors and sign-up are refused, and that guessed passwords are refused. It then signs in, sends a heartbeat with the API key, reads the heartbeat back and checks that a wrong key is refused. Finally, it removes its test heartbeat and checks the listening ports:
sudo wakapi-selftestSELFTEST-OK https://<public-ip>/ : anonymous refused, sign-up refused, guessed passwords refused, admin signed in, heartbeat stored + read back with the API key (wrong key 401), listeners 22/80/443Your own DNS name and certificate
Point a DNS name at the VM's public IP address (an Azure public IP can carry a DNS label such as <label>.<region>.cloudapp.azure.com). Then set it as the name Wakapi is served as. This command writes the name into the nginx configuration and Wakapi's public URL, generates a new self-signed certificate for it and restarts Wakapi:
sudo wakapi-set-hostname <your-domain>To use a certificate from your own certificate authority (or one you obtained for the name), replace /etc/ssl/wakapi/wakapi.crt and /etc/ssl/wakapi/wakapi.key. Keep the key at mode 0600, owner root. Then reload nginx with sudo systemctl reload nginx.
Running wakapi-set-hostname again generates a new self-signed certificate, so install your own after it. Once a CA-issued certificate is in place, remove no_ssl_verify from the workstations' ~/.wakatime.cfg. Never add the VM's self-signed certificate to a trust store.
Check the security posture from the command line
H=$(sudo sed -n 's#^WAKAPI_URL=https://\([^/]*\)/#\1#p' /var/lib/sac/data/wakapi-credentials.txt) c() { curl -sk --resolve "$H:443:127.0.0.1" --resolve "$H:80:127.0.0.1" -o /dev/null "$@"; } c -w 'port 80: %{http_code} -> %{redirect_url}\n' "http://$H/" c -w 'API without a key: %{http_code}\n' "https://$H/api/summary?interval=today" c -w 'heartbeat with a wrong key: %{http_code}\n' -u 'not-a-key:' -H 'Content-Type: application/json' --data '[]' "https://$H/api/heartbeat" c -w '%{http_code}\n' -X POST --data 'username=someone&password=secret123&password_repeat=secret123' "https://$H/signup" | sed -E 's/^(403|429)$/sign-up: refused/' c -w 'cross-origin form post: %{http_code}\n' -X POST -H 'Origin: https://evil.example' "https://$H/login" c -w 'API explorer (/swagger-ui): %{http_code}\n' "https://$H/swagger-ui/" c -w 'compressed request body: %{http_code}\n' -X POST -H 'Content-Encoding: gzip' --data 'x' "https://$H/api/heartbeat" curl -s -o /dev/null -w 'unknown host name: %{http_code} (connection closed)\n' -H 'Host: other.example' http://127.0.0.1/ || trueport 80: 301 -> https://<public-ip>/ API without a key: 401 heartbeat with a wrong key: 401 sign-up: refused cross-origin form post: 403 API explorer (/swagger-ui): 404 compressed request body: 415 unknown host name: 000 (connection closed)The sign-up attempt is refused with 403 ("registration is disabled"), or with 429 once the sign-up rate limit for your address is reached.
Wakapi, nginx and the first-boot service cannot reach the Azure metadata service or the WireServer. Wakapi's outbound requests go only to the allowed hosts:
for u in wakapi nginx wakapi-firstboot; do echo "$u denies: $(systemctl show -p IPAddressDeny --value $u | tr ' ' '\n' | sort | xargs)"; done
sudo grep -E '^(HTTPS_PROXY|NO_PROXY)=' /etc/wakapi/egress.envwakapi denies: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 nginx denies: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 wakapi-firstboot denies: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 HTTPS_PROXY=http://127.0.0.1:9 NO_PROXY=api.wakatime.com,wakapi.devHTTPS_PROXY points at a port where nothing listens, so any request to a host that is not in NO_PROXY fails before a connection is made. The metadata block applies even to a host you add to NO_PROXY. You can see the kernel block yourself. The same request to the metadata service, run in a throwaway unit with the same rule, gets no answer:
sudo systemd-run --quiet --wait --pipe -p IPAddressDeny="169.254.169.254/32 168.63.129.16/32" curl -s -m 5 -o /dev/null -w 'metadata from a denied unit: %{http_code}\n' -H 'Metadata: true' 'http://169.254.169.254/metadata/instance?api-version=2021-02-01' || truemetadata from a denied unit: 000Server components
- Wakapi binary: /usr/local/bin/wakapi
- Configuration: /etc/wakapi/config.yml (root:wakapi, mode 0640)
- Per-VM salt and cookie key: /etc/wakapi/wakapi.env (root, mode 0600)
- Outbound allowlist: /etc/wakapi/egress.env
- Database (SQLite): /var/lib/wakapi/wakapi.db
- Service units: wakapi.service, nginx.service, wakapi-firstboot.service, wakapi-address.service
- nginx site: /etc/nginx/sites-available/sac-wakapi (closed routes: /etc/nginx/wakapi-closed.conf)
- Certificate and key: /etc/ssl/wakapi/
- First-boot script: /usr/local/sbin/wakapi-firstboot.sh
- Address reader: /usr/local/sbin/wakapi-address (writes /run/sac-wakapi/address.env)
- Host name helper: /usr/local/sbin/wakapi-set-hostname
- Self test: /usr/local/sbin/wakapi-selftest
- Credentials: /var/lib/sac/data/wakapi-credentials.txt (mode 0600)
/usr/local/bin/wakapi -version command -v go || echo "no Go toolchain" nginx -v 2>&1 sed -n '1,5p' /usr/share/doc/sac-wakapi/BUILD-INFO.txt2.18.2 no Go toolchain nginx version: nginx/1.24.0 (Ubuntu) wakapi 2.18.2 (tag 2.18.2, commit 1157f203c8b646466b4e72563764738b10f0cd0d), MIT - built on the build instance from the tag https://github.com/muety/wakapi/archive/refs/tags/2.18.2.tar.gz (sha256 1c77089b75b6e88dbb984708d0a03476867913e29c61b09bf783063336e73e1e) go1.27.3 (official tarball, sha256 65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40; not shipped), CGO_ENABLED=0, GOEXPERIMENT=greenteagc,jsonv2, -trimpath, -ldflags '-w -s'; binary sha256 7a121ee6531fa3c2259f7e3300714db4deab9880b9e189fc30f00978584f9156 (reproducible: identical to the binary audited off-instance)/usr/share/doc/sac-wakapi/BUILD-INFO.txt records the source and toolchain checksums, the dependency update, the bundled web libraries and the govulncheck result. The files go.mod, go.sum and govulncheck-wakapi-source.txt sit beside it.
Backups: Wakapi keeps everything in the SQLite database /var/lib/wakapi/wakapi.db on the OS disk. Use Azure Backup for the VM, or take a snapshot of the OS disk. For a consistent copy, run sudo systemctl stop wakapi, take the snapshot, then run sudo systemctl start wakapi.
SecureAnyCloud made these changes to the operating system:
- SSH does not allow root logins, empty passwords or X11 forwarding. Password sign-in follows the choice you made when you created the VM.
- The root account has no password.
- The firmware metadata refresh timer (fwupd-refresh) is masked, because an Azure VM has no updatable firmware.
- Core dumps are off for the Wakapi services.
- The golden image's boot-time updater is removed.
- Unattended security upgrades are on.
Security notes and recommendations
- Allow ports 22, 80 and 443 in the network security group from your own networks only.
- Change the administrator password after your first sign-in (Step 5), keep the credentials file secret, and give each person their own account and API key (Step 8).
- Use your own DNS name and a CA-issued certificate (Step 10), then drop
no_ssl_verifyfrom the plugins' configuration. Until then, check the self-signed certificate's fingerprint (Step 5) instead of trusting it. - Send API keys in the
Authorizationheader, as the WakaTime plugins do, instead of as anapi_key=URL parameter. Request URLs are written to the nginx and Wakapi logs. - govulncheck lists one advisory for a module in Wakapi's build whose vulnerable package Wakapi never uses, so no vulnerable code is reachable: GO-2026-5932 (the golang.org/x/crypto/openpgp package is unmaintained, and there is no fixed version).
- The built-in API explorer bundles DOMPurify 2.3.10. nginx closes its /swagger-ui route on this image, so use the API reference at https://wakapi.dev/swagger-ui/ instead. SecureAnyCloud rechecks both items at each Wakapi release.
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Wakapi is open source under the MIT licence (Ferdinand Mütsch and contributors). The licence text is in /usr/share/doc/sac-wakapi/, and the source of this exact release is at https://github.com/muety/wakapi/tree/2.18.2. Its built-in web UI includes open source libraries under the MIT licence and Iconify under the Apache 2.0 licence.
WakaTime is a trademark of its owner. Wakapi and this image are not affiliated with or endorsed by WakaTime. There is no per-user or per-CPU licence fee. The image price covers SecureAnyCloud's build, hardening, first-boot setup and 24/7 support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on Azure
Launch Wakapi 2.18.2 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
Wakapi is a self-hosted, WakaTime-compatible coding-activity and statistics server. It receives heartbeats from the standard WakaTime editor plugins for Visual Studio Code, JetBrains IDEs, Vim, Sublime Text and dozens more. It then renders dashboards of how much time you spend per project, per programming language, per editor and per machine, with daily and ranged summaries and an all-time overview.
Because Wakapi speaks the WakaTime API, you point your existing editor setup at your own server by changing a single line of configuration. Your coding history stays entirely on infrastructure you control.
This guide covers deploying and configuring Wakapi on AWS, using the SecureAnyCloud AWS Marketplace AMI. The image ships the free and open source, MIT-licensed Wakapi (muety/wakapi), installed as the official single Go binary and reverse-proxied behind nginx. The SQLite activity database sits on a dedicated data volume, so your instance starts in seconds.
What you get:
- Wakapi 2.17.5 (the MIT-licensed, self-hosted, WakaTime-compatible coding statistics server), installed as the official single Go binary at
/usr/local/bin/wakapi. - An nginx reverse proxy publishing the web UI on port
80, ready for your TLS certificate. Wakapi itself binds only to loopback127.0.0.1:3000and is reached solely through nginx. - The SQLite activity database on a dedicated gp3 data volume mounted at
/var/lib/wakapi, on its own independently resizable disk. wakapi.service,nginx.serviceandwakapi-firstboot.serviceas systemd units, enabled and active on boot.- A per-instance admin account with a random password, and a per-instance password salt. Both are generated on each machine's first boot, before the server is reachable, and are never baked into the image.
- No default login, no shipped secret, public self sign-up disabled and an empty database on first boot. The server is single-tenant rather than an open registration service.
- A fully patched Ubuntu 24.04 LTS base with unattended security upgrades.
- 24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA.
Wakapi is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by the Wakapi project. It ships the free and open source, MIT-licensed, self-hosted software, unmodified.
Connecting to your instance
Connect over SSH on port 22 as the default login user for the operating system variant you launched. For Ubuntu 24.04, the login user is ubuntu:
ssh ubuntu@<vm-ip>Prerequisites
- An AWS account subscribed to the Wakapi listing on AWS Marketplace.
- A key pair in the target region for SSH access.
- A security group that allows inbound TCP
22(SSH) and80(web UI) from your IP ranges.
Launch from the AWS Marketplace console
Subscribe to the Wakapi listing and choose Launch through EC2. Pick the recommended m5.large instance type (or larger), and select your key pair and a security group that allows ports 22 and 80. Then launch.
The dedicated data volume for the activity database is attached automatically.
Launch from the AWS CLI
aws ec2 run-instances \
--image-id ami-xxxxxxxxxxxxxxxxx \
--instance-type m5.large \
--key-name your-key \
--security-group-ids sg-xxxxxxxx \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wakapi}]'Connect to your instance
ssh ubuntu@<vm-ip>Confirm the services are running
Wakapi runs as a single Go binary under wakapi.service, fronted by nginx.service. Both services should be active:
systemctl is-active wakapi.service nginx.serviceRead the per instance credentials
The first boot generates a unique admin password, password salt and WakaTime API key for this instance. It writes them to a root-only file. Read the file:
sudo cat /root/wakapi-info.txtThe file shows the admin username, its random password, the account's WakaTime API key and the resolved WAKAPI_URL for this instance. Keep the file safe. Nothing in it is shared between instances, and nothing was baked into the image.
Verify the security model
Confirm that the login page is served over HTTP on port 80, that Wakapi listens only on loopback, and that public sign-up is refused:
# login page served through nginx on port 80 curl -s -o /dev/null -w 'login page: HTTP %{http_code}\n' http://localhost/login
# wakapi binds to loopback only (never the public interface)
sudo ss -ltnp | grep -q '127.0.0.1:3000' && echo 'wakapi bound to loopback 127.0.0.1:3000 only'
# a public sign-up attempt is refused (single-tenant appliance)
curl -s -o /dev/null -w 'signup attempt: HTTP %{http_code} (403 = disabled, as expected)\n' \
--data-urlencode 'username=notallowed' --data-urlencode 'password=abcd1234efgh' \
--data-urlencode 'password_repeat=abcd1234efgh' --data-urlencode 'location=UTC' \
http://localhost/signupSign in and load the dashboard
Log in with the per-instance admin credentials and confirm that the statistics dashboard responds:
CJ=$(mktemp)
curl -s -o /dev/null -c "$CJ" \
--data-urlencode 'username=admin' --data-urlencode 'password=<WAKAPI_ADMIN_PASSWORD>' \
http://localhost/login
curl -s -o /dev/null -w 'dashboard: HTTP %{http_code}\n' -b "$CJ" http://localhost/summary rm -f "$CJ"In a browser, open http://<vm-ip>/ and sign in with the username admin and the password from /root/wakapi-info.txt. The dashboard breaks your activity down per project, per language and per editor.
Point your editor at your Wakapi server
Wakapi speaks the WakaTime API, so any editor with the WakaTime plugin can send heartbeats to it. Install the WakaTime plugin for your editor, then set its config file to point at your server. This edits a file on your workstation, not on the instance:
# ~/.wakatime.cfg on YOUR workstation (edit your-domain to your instance URL)
# [settings]
# api_url = http://your-domain/api
# api_key = <the WAKAPI_API_KEY from /root/wakapi-info.txt> echo 'Set api_url and api_key in ~/.wakatime.cfg using the wakatime-cli plugin settings'As you code, heartbeats flow to your server and the dashboards fill in. You can also find and reset the account's API key under Settings.
Explore per-project statistics
Every project you work on gets its own drill-down, with its languages, editors, branches, files and hourly breakdown. You can see exactly where your time goes on a single codebase.
Confirm the pinned release
/usr/local/bin/wakapi -versionImport history (optional)
From the Settings page, Wakapi can import your existing coding history from a WakaTime account or another Wakapi instance. You do not lose your past activity when you self-host.
Server components
The appliance is intentionally minimal:
- Wakapi runs as
wakapi.service, a single Go binary bound to127.0.0.1:3000. It reads/etc/wakapi/config.ymlplus the per-instance overrides in/etc/wakapi/wakapi.env. - nginx (
nginx.service) is the only public listener. It reverse-proxies port80to Wakapi and is ready for your TLS certificate. - The SQLite activity database lives at
/var/lib/wakapi/wakapi.dbon a dedicated gp3 data volume. It is a file and is never network-reachable. - wakapi-firstboot.service generates the per-instance salt and admin account on first boot and then disables itself.
Managing the service
Manage the stack with standard systemctl commands. These commands change runtime state, so run them only when you intend to:
sudo systemctl restart wakapi.service
sudo systemctl restart nginx.serviceUse your own domain and HTTPS (production)
For production, front the instance with your own domain and a certificate. Point a DNS record at the instance, then terminate TLS either at an AWS load balancer or on the instance with certbot:
# on the instance, after pointing your-domain at it
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domainWakapi derives its public URL from WAKAPI_PUBLIC_URL in /etc/wakapi/wakapi.env. After you enable TLS, update it to https://your-domain/.
Security recommendations
- Change the admin password after your first login, under Settings.
- Restrict the security group for port
80to known ranges, or place the instance behind a load balancer that terminates TLS. - Keep public sign-up disabled (the default) so the server stays single-tenant. Add users deliberately if you need them.
- Take regular snapshots of the
/var/lib/wakapidata volume to back up your activity database.
Support and Licensing
Wakapi is distributed under the MIT license. This is a repackaged open source product. SecureAnyCloud's charges are for support services, not for the software itself.
SecureAnyCloud provides 24/7 technical support with a guaranteed 24 hour response SLA. Email sales@secureanycloud.com or use live chat for help with deployment, TLS termination, editor plugin configuration, backups, user management and scaling.
Deploy on AWS
Find Wakapi on AWS Marketplace and launch it with SecureAnyCloud's preconfigured AMI.
Deploy on AWS
Launch Wakapi on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com