Medama
Overview
Medama is a privacy-focused, cookieless, self-hosted web analytics server. It is a lightweight, single-binary alternative to hosted analytics services. This image gives you fast visitor insights (page views, unique visitors, referrers, top pages, countries, browsers, operating systems and devices) from a clean dashboard. It also serves a tiny tracking script that sets no cookies and collects no personal data.
Your analytics data is stored locally on your own server and never leaves infrastructure that you control. There are no consent banners to manage, and nothing is shared with a third party.
The Medama server is a single self-contained Go binary that serves both the REST API and the bundled dashboard web app. It uses an embedded SQLite metadata store and an embedded DuckDB analytics store, so no external database or runtime interpreter is required. It runs behind nginx as a reverse proxy on port 80. Medama has no listen address option of its own, so an nftables guard confines its port 8080 to the loopback interface. nginx on port 80 is the only public surface.
On the first boot of every deployed VM, a one-shot service rotates the administrator password to a value unique to that VM. It records the login in /root/medama-credentials.txt with mode 0600, and only then starts nginx. Port 80 never answers before that rotation has finished, and the upstream default admin / CHANGE_ME_ON_FIRST_LOGIN login never works on a deployed VM. Two VMs deployed from the same image never share credentials.
What you get:
- Medama 0.6.2 server (single self-contained Go binary that bundles the dashboard web app, with embedded SQLite and DuckDB) at
/usr/local/bin/medama - nginx reverse proxy on
:80in front of the Medama server on loopback:8080 - An nftables guard that confines Medama's port 8080 to the loopback interface
- A per-VM administrator password generated at first boot, in a root-only file. No working default login ships in the image.
medama.service,nginx.serviceand a one-shotmedama-firstboot.serviceas systemd units- A fully patched Ubuntu 24.04 LTS security baseline at capture time, with unattended security updates enabled
- 24/7 SecureAnyCloud support
Prerequisites
- An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region
- Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. Medama is very lightweight and runs happily on small instances.
- NSG inbound rules: allow
22/tcpfrom your management network. Allow80/tcp(plus443/tcponce you enable HTTPS) from the networks that your dashboard users and tracked websites reach Medama on. - The tracking endpoint must be reachable from your visitors' browsers. Open port 80/443 to the public if you collect analytics from a public website.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Medama by SecureAnyCloud, and select Create.
On Basics, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then choose Review + create, and then Create. First-boot initialisation takes a few seconds after the VM starts.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:medama-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name medama \
--image sac1647283583153:medama-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name medama --port 80 --priority 1010Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>Step 4 - Verify the Medama stack
Confirm that the services are active and that the dashboard answers through nginx:
systemctl is-active medama nginx curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/Both service lines read active and the dashboard returns 200.
Medama itself listens on port 8080. It has no loopback bind option, so an nftables guard drops any connection to port 8080 on the VM's routable address. Only nginx on port 80 is reachable from outside. Confirm the guard from the VM:
IP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -w 'port 8080 on %{url_effective}: %{http_code}\n' -m 4 "http://$IP:8080/" || echo "port 8080 refused on $IP"
curl -s -o /dev/null -w 'nginx :80: %{http_code}\n' http://127.0.0.1/The request to port 8080 on the routable IP is refused (000 / no response), and nginx on port 80 answers 200.
Step 5 - Retrieve your administrator login
The login for the admin account is generated on the first boot of your VM and stored in a root-only file. The file lists the Medama URL, the administrator username (admin) and the password:
sudo cat /root/medama-credentials.txtStore the password in your password manager and treat the file as sensitive. It is mode 0600, readable only by root:
ls -l /root/medama-credentials.txtYou can change the password at any time from the dashboard (top right, Settings, then Account).
Step 6 - Verify authentication
Medama authenticates dashboard requests with a session cookie. An unauthenticated request to the dashboard API is refused. Logging in with the per-VM password sets a session that then reads back your user. The upstream default admin / CHANGE_ME_ON_FIRST_LOGIN is rejected:
PASS=$(sudo grep '^medama.admin.pass=' /root/medama-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'no session: %{http_code}\n' http://127.0.0.1/api/user
CJ=$(mktemp)
curl -s -o /dev/null -w 'login: %{http_code}\n' -c "$CJ" -X POST http://127.0.0.1/api/auth/login \
-H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"$PASS\"}" echo -n 'with session: ';
curl -s -b "$CJ" http://127.0.0.1/api/user | jq -r '.username'
curl -s -o /dev/null -w 'default pass: %{http_code}\n' -X POST http://127.0.0.1/api/auth/login \
-H 'Content-Type: application/json' -d '{"username":"admin","password":"CHANGE_ME_ON_FIRST_LOGIN"}' rm -f "$CJ"The unauthenticated call reports 401, the login reports 200, the authenticated call prints admin, and the upstream default reports 401. This shows that the shipped image has no working default credential.
Step 7 - First sign-in to the dashboard
Open a web browser and go to http://<vm-public-ip>/. Sign in with the username admin and the password from /root/medama-credentials.txt. The dashboard opens on My Websites. From there you register the sites that you want to track and open each site's analytics.
Step 8 - Register a website
On the My Websites page, choose Add Website and enter the hostname of the site that you want to track (for example www.example.com). Medama attributes incoming pageviews to a registered website by the hostname in the page URL. The hostname must match the site that the tracking script runs on.
Step 9 - Install the cookieless tracking snippet
Open Settings, then Tracker, and copy the tracking snippet. Paste it into the <head> of every page of your website. The script is tiny, sets no cookies and collects no personal data:
<script defer src="http://<vm-public-ip>/script.js"></script>Replace <vm-public-ip> with your VM's public IP or, better, the DNS name that you serve Medama on (see Enabling HTTPS below). On the Tracker page you can also enable optional click tracking and page-view event tracking.
Step 10 - View your analytics
Once the snippet is live, visits to your site appear in Medama in real time. Open a website from My Websites to see its dashboard. It shows unique visitors, page views, average time spent and bounce rate at the top, a visitors-over-time graph, and breakdowns by page, referrer, country, browser, operating system and device. Use the period selector (top right) to switch between today, the last few days, and longer ranges.
You can confirm end-to-end ingestion from the VM. Register a test website over the API, send a simulated browser pageview to the public ingest endpoint, and read the count back through the authenticated stats API:
PASS=$(sudo grep '^medama.admin.pass=' /root/medama-credentials.txt | cut -d= -f2-)
CJ=$(mktemp); SITE="test.example.com" curl -s -c "$CJ" -X POST http://127.0.0.1/api/auth/login -H 'Content-Type: application/json' \
-d "{\"username\":\"admin\",\"password\":\"$PASS\"}" >/dev/null curl -s -o /dev/null -b "$CJ" -X POST http://127.0.0.1/api/websites -H 'Content-Type: application/json' \
-d "{\"hostname\":\"$SITE\"}" UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' curl -s -o /dev/null -H "User-Agent: $UA" -H "X-Forwarded-For: 203.0.113.45" "http://127.0.0.1/api/event/ping?u=http://$SITE/" curl -s -o /dev/null -X POST http://127.0.0.1/api/event/hit -H 'Content-Type: application/json' \
-H "User-Agent: $UA" -H "X-Forwarded-For: 203.0.113.45" \
-d "{\"b\":\"$(openssl rand -hex 8)\",\"e\":\"load\",\"u\":\"http://$SITE/\",\"r\":\"https://www.google.com/\",\"p\":true,\"q\":true,\"t\":\"UTC\"}"
ST=$(date -u -d '-1 day' '+%Y-%m-%dT%H:%M:%SZ');
EN=$(date -u -d '+1 hour' '+%Y-%m-%dT%H:%M:%SZ')
echo -n 'recorded page views: ' curl -s -b "$CJ" "http://127.0.0.1/api/website/$SITE/summary?start=$ST&end=$EN&interval=hour" | jq -r '.current.pageviews' rm -f "$CJ"The final line prints 1 (or more). This confirms that the pageview was ingested through the public endpoint and read back through the authenticated dashboard API. By design, Medama ignores traffic that looks like a bot or comes from a non-genuine source, so this test shapes the request like a real browser visit.
Security baseline
The image is captured with the Ubuntu 24.04 security baseline fully applied (including phased updates). It ships with unattended security upgrades enabled, so your VM keeps patching itself after deployment.
The shipped image contains no database and no user account. The default administrator is re-seeded on your VM's first boot on the loopback interface only, and it is immediately rotated to a per-VM password before nginx exposes port 80. Port 80 is gated on the first-boot marker.
sudo nft list table inet medama_guard | grep 'dport 8080' grep -h ConditionPathExists /etc/systemd/system/nginx.service.d/*.conf grep -h Unattended-Upgrade /etc/apt/apt.conf.d/20auto-upgradesEnabling HTTPS
For production use, serve Medama over HTTPS. This encrypts logins and tracking in transit, and lets the tracking snippet load securely on HTTPS sites. Point a DNS A record at the VM's public IP address, then install a certificate with Certbot (replace the domain):
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comAfter enabling HTTPS, open 443/tcp in the NSG and restrict or close inbound port 80. Update the tracking snippet on your website to load https://your-domain.example.com/script.js.
Backup and maintenance
All Medama state lives under /var/lib/medama. It holds the SQLite metadata database (me_meta.db, with your user, settings and registered websites) and the DuckDB analytics database (me_analytics.db, with your pageview events). The simplest logical backup is to copy that directory while the service is stopped, or to snapshot the OS disk:
sudo systemctl stop medama
sudo tar czf /var/backups/medama-$(date +%F).tar.gz -C /var/lib medama
sudo systemctl start medamaApply operating system security updates with sudo apt update && sudo apt upgrade. The services restart cleanly. Restart the server at any time with sudo systemctl restart medama.
Licence and source
Medama's server and dashboard are free software licensed under the Apache License 2.0. The small client-side tracking script served at /script.js is licensed under the MIT License. The upstream project and source are at the Medama repository.
This image ships the official Medama 0.6.2 release binary unmodified. SecureAnyCloud supplies only the surrounding Ubuntu image, the nginx reverse proxy, the nftables guard and the first-boot configuration.
Support
This image is backed by 24/7 SecureAnyCloud support. Contact us by email or chat for help with deployment, website and tracker configuration, TLS termination and analytics questions.
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.
Deploy on Azure
Launch Medama 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
Medama is a self-hostable, privacy-focused website analytics platform. It gives you clear, real-time insight into your website traffic without cookies, without logging IP addresses and without any additional visitor identifiers. This helps you stay compliant with GDPR, PECR and similar regulations. It still answers the questions that matter: which pages are popular, where visitors come from, which referrers and campaigns drive traffic, and how your audience breaks down by browser, operating system, device and country.
Visitors are measured with a lightweight tracker script of less than one kilobyte. The whole platform is a single self-contained Go binary with two embedded databases, so it stays lightweight and starts instantly.
The SecureAnyCloud image installs Medama and puts it behind an nginx reverse proxy. The appliance is secure by default. The default security group does not expose the internal service port to the internet, and there is no shared default password. A unique administrator credential is generated automatically on first boot and stored in a root-only file on the instance. It is different on every machine you launch, so no secret is ever baked into the image.
Medama sets a secure session cookie, so signing in requires a localhost or HTTPS connection. This guide walks you through both a simple SSH tunnel and enabling HTTPS on your own domain.
What you get:
- Medama single Go binary (
/usr/local/bin/medama), version 0.6.2 - Two embedded databases in
/var/lib/medama, captured empty:me_meta.db(SQLite: users and websites) andme_analytics.db(DuckDB: analytics events) medama.servicerunning as themedamauser on internal port 8080nginx.servicereverse proxy on TCP 80, TLS readymedama-firstboot.service, which rotates the administrator password to a unique per-instance value on first boot and writes it to a root-only file- A real-time analytics dashboard and a sub-1KB cookieless tracker served at
/script.js - Ubuntu 24.04 LTS base, latest patches, unattended security upgrades enabled
- 24/7 SecureAnyCloud support
Prerequisites
Before you deploy this image, you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and inbound ports 80 and 443 from the networks that your websites and users reach the analytics server on
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
Connecting to your instance
Connect over SSH on port 22 as the default login user for the operating system variant that you launched. This listing currently ships one variant: Ubuntu 24.04 LTS, with the SSH login user ubuntu.
Medama runs on internal port 8080 and is served to the network through nginx on port 80. The security group opens ports 22, 80 and 443 only, so the internal port is never reachable directly from the internet.
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Medama. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of m5.large or larger. Medama is very light, but m5.large is the tested recommendation. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet. Create or select a security group that allows inbound port 22 from your management network and inbound ports 80 and 443 from the networks that your websites and users use. Leave the root volume at the default size or larger.
Select Launch instance. First-boot initialisation, including generating the per-instance administrator credential, takes under a minute after the instance state becomes Running and the status checks pass.
Launch the Instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Medama Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22, 80 and 443 as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=medama}]'On first boot, the image resolves this instance's public address using the EC2 Instance Metadata Service (IMDSv2), so it works with HttpTokens=required enabled.
Connect via SSH
Connect as the ubuntu user (see Connecting to your instance above).
ssh ubuntu@<public-ip>As soon as you log in, the message of the day prints the tracker host URL, the dashboard tunnel command and the key operational commands.
Retrieve your per-instance administrator credential
On first boot, the image generates a unique administrator password for this instance. It writes the password, together with the resolved URLs, to a root-only file. Read the file with sudo:
sudo cat /root/medama-credentials.txtThe file shows the username (always admin), the generated password, and the exact SSH tunnel command and tracker host for this instance:
# Medama on AWS - Per-VM Administrator Credential MEDAMA_ADMIN_USERNAME=admin MEDAMA_ADMIN_PASSWORD=<MEDAMA_ADMIN_PASSWORD> MEDAMA_DASHBOARD_TUNNEL=ssh -L 8080:localhost:8080 ubuntu@<public-ip> MEDAMA_DASHBOARD_LOCAL=http://localhost:8080 MEDAMA_TRACKER_URL=http://<public-ip>/The password is unique to this instance and is not present in the AMI. Keep it safe. You can change it later from within the dashboard.
Open the dashboard over an SSH tunnel
Medama sets a secure session cookie, so it allows sign-in only from a localhost or HTTPS origin. The simplest way to reach the dashboard securely is an SSH tunnel that forwards a local port to Medama on the instance. From your own workstation, run:
ssh -L 8080:localhost:8080 ubuntu@<public-ip>Leave that session open, then browse to http://localhost:8080 on your workstation. Medama prompts you to sign in.
Enter the username admin and the password from /root/medama-credentials.txt. After you sign in, you land on the My Websites page, which is empty until you add your first site.
You can also verify the login and administrator credential from the command line on the instance. This command logs in over loopback and confirms that the session is valid:
COOKIE=$(curl -s -D - -o /dev/null -m 15 -X POST http://127.0.0.1:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<MEDAMA_ADMIN_PASSWORD>"}' \
| tr -d '\r' | grep -i '^set-cookie:' | grep -o '_me_sess=[^;]*' | head -1)
curl -s -o /dev/null -w 'authenticated /api/user -> HTTP %{http_code}\n' \
-H "Cookie: $COOKIE" http://127.0.0.1:8080/api/userA 200 confirms the credential works.
Add a website and install the tracking snippet
In the dashboard, select Add Website and enter the hostname of the site that you want to measure (for example www.example.com). Then add the Medama tracking snippet to the <head> of that site's pages. Point it at this instance's tracker host (the MEDAMA_TRACKER_URL value from your credentials file):
<script defer src="http://<public-ip>/script.js"></script>The tracker is served unauthenticated through nginx, so your website's visitors can send page views to the instance while the dashboard stays protected behind the tunnel. Once traffic starts flowing, open the site from My Websites to see live statistics: visitors, page views, and the pages that your audience is viewing.
Explore your analytics
Medama breaks traffic down across several dimensions. The Referrers, Sources, Mediums and Campaigns tabs show where your visitors come from, including UTM-tagged campaigns.
The Browsers, OS and Devices panels, along with Countries and Languages, describe your audience. All of this is inferred without cookies or personal identifiers.
Use the date range selector at the top of the dashboard to move between today, the last few days, and longer periods.
Managing the services
Medama and nginx are managed with systemctl. Check their status at any time:
systemctl status medama.service nginx.serviceBoth should report active (running). After any configuration change, restart Medama with sudo systemctl restart medama and reload nginx with sudo systemctl reload nginx. Confirm the Medama version with:
medama versionEnabling HTTPS
Serving the dashboard over HTTPS lets you and your team sign in directly from a browser without the SSH tunnel. It is strongly recommended for any production deployment. You have two options.
Option A - Medama's built-in automatic SSL. Point a DNS A record at this instance's public IP. Then configure Medama to provision and renew a Let's Encrypt certificate for that domain by adding the -autossl flag (and optionally -autosslemail) to the service. Edit /etc/systemd/system/medama.service, append -autossl your-domain.com to the ExecStart line, then run sudo systemctl daemon-reload && sudo systemctl restart medama. Medama then serves HTTPS on port 443 directly. Make sure that your security group allows inbound 443.
Option B - terminate TLS at nginx. Install a certificate with certbot's nginx plugin (sudo apt-get install -y certbot python3-certbot-nginx && sudo certbot --nginx -d your-domain.com) and let nginx continue to proxy to Medama on 127.0.0.1:8080. Once HTTPS is in place, you can sign in to the dashboard directly at your domain, without the SSH tunnel.
Maintenance and upgrades
The base operating system has unattended security upgrades enabled, so OS patches are applied automatically. To upgrade Medama itself to a newer release, download the latest Linux binary from the Medama releases page, replace /usr/local/bin/medama, and restart the service:
sudo systemctl stop medama
# replace /usr/local/bin/medama with the new binary, then:
sudo systemctl start medamaYour analytics data in /var/lib/medama is preserved across upgrades. Back up that directory to retain your history.
Support
SecureAnyCloud provides 24/7 technical support for this product via email and live chat.
- Email: sales@secureanycloud.com
- Live chat: available 24/7 on secureanycloud.com
We help with deployment and instance configuration, upgrades to new Medama releases, adding websites and installing the tracking snippet, TLS and certificate setup, retrieving and rotating the administrator credential, and performance tuning. Critical issues receive a one-hour average response.
This is a repackaged open source software product with additional charges for SecureAnyCloud support services. Medama's core and dashboard are distributed under the Apache License 2.0 and its tracker under the MIT License. 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.
Deploy on AWS
Launch Medama on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com