Dashy 4.9.0
Overview
This guide covers launching and using the Dashy 4.9.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud developer and API tools listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
Dashy is an open source, self-hosted dashboard and start page. It puts links to every application, service and bookmark you use on one page, grouped into sections with icons, themes, keyboard search, widgets and live status checks that show which services are up. The layout lives in one YAML file that you can edit on the server or in Dashy's built-in editor.
Upstream Dashy has no login of its own by default. Its optional login is enforced in the browser, not on the server, so anyone who can reach Dashy can read and change the dashboard. The SecureAnyCloud image turns it into a private dashboard for your team.
What you get:
- HTTPS from the first boot. nginx serves Dashy on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password unique to your instance, on every path. HTTP Basic Auth is required for the user
adminon every page, asset, configuration file (/conf.ymlin any spelling or encoding) and server endpoint (config saving, status checks, the CORS proxy). The password is generated on first boot and written to/var/lib/sac/data/dashy-credentials.txt(root only). nginx stores only a bcrypt hash of it. No password is baked into the image. - Status checks and the CORS proxy cannot reach the metadata service. Dashy's server fetches URLs for its status checks, ping checks and CORS proxy. The Dashy service and nginx are blocked from the EC2 instance metadata service at the kernel level. These fetch endpoints answer only requests from Dashy's own pages, and a request that another web site makes through your browser is refused.
- Fail closed. Dashy and nginx refuse to start until first boot has generated the certificate and the password.
- Hardened defaults. Dashy's Node.js server listens on 127.0.0.1 only and runs as an unprivileged
dashyuser that can write nothing but its configuration directory. Requests for any host name the instance does not serve are dropped, cross-site requests that change state are refused, and the AMI requires IMDSv2. - Patched dependencies, no build tools on the image. Dashy is built from the pinned 4.9.0 release with its two vulnerable JavaScript dependencies (body-parser, fast-uri) raised to fixed versions in the same major versions. The shipped packages have no known npm advisories at build time. The Node.js 24 LTS runtime ships without npm, yarn or a compiler.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Dashy 4.9.0 (/opt/dashy), Node.js 24.22.1 LTS (/opt/node), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
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), TCP 443 (Dashy) and TCP 80 (redirects to HTTPS) from your own networks only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Dashy 4.9.0 on Ubuntu 24.04 as the software version, choose your Region, then choose Launch through EC2.
Choose m5.large, your key pair and a security group as above. Dashy itself needs little, so a smaller instance works for a small team. To run status checks against services in your VPC, launch Dashy in a subnet that can reach them.
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> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=dashy}]'Connect to your instance
For Dashy 4.9.0 on Ubuntu 24.04, the SSH user is ubuntu. Connect with:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates the certificate and the password, then Dashy and nginx start:
systemctl is-active dashy nginx dashy-firstboot
sudo test -f /var/lib/sac/data/dashy-firstboot.done && echo FIRSTBOOT_DONEAll three units are active and the first-boot marker is present:
active active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network. Dashy's Node.js server (MainThread) listens on 127.0.0.1 only:
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:4000 users:(("MainThread")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"))Get your URL and password
sudo cat /var/lib/sac/data/dashy-credentials.txtThe file is readable by root only. It holds the URL, the user name and this instance's password:
DASHY_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ DASHY_USERNAME=admin DASHY_PASSWORD=<DASHY_PASSWORD>The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve Dashy under your own DNS name, follow Step 9.
Sign in from your browser
Open the DASHY_URL in your browser. The certificate is self-signed, so your browser asks you to confirm it the first time. To avoid the prompt, install your own certificate in Step 9. Sign in as admin with the password from Step 4. The browser remembers the login for the session.
The image starts with a small SecureAnyCloud dashboard. It has a Getting Started section with links to this guide and the Dashy documentation, and a Your Services section for you to replace with your own:
Edit the dashboard in the browser
Open the options panel with the wrench icon at the top right, then choose Config. The configuration menu shows the running version and where the configuration comes from (/conf.yml):
Edit Config opens the YAML editor with validation. Save to Disk writes the file on the server (/opt/dashy/user-data/conf.yml) for every user. It keeps a backup of the previous version in /opt/dashy/user-data/config-backups/. Save Locally keeps the change in your own browser only:
Saving to disk accepts only .yml and .yaml file names and writes only inside /opt/dashy/user-data. The rest of the server is read-only for the Dashy service.
Enable Cloud Sync is optional and off. It would upload your configuration to Dashy's own sync service, so leave it off unless you want that.
Add your services and status checks from the command line
The dashboard is one YAML file. The following command replaces it with two sections and turns on status checks, which make Dashy's server request each item's URL and show a green or red dot. Dashy picks up the file on the next page load:
sudo tee /opt/dashy/user-data/conf.yml >/dev/null <<'YAML' pageInfo: title: My Services description: Everything we run, on one page appConfig: theme: colorful layout: auto statusCheck: true statusCheckInterval: 300 sections: - name: Websites icon: fas fa-globe items: - title: sac description: sac website icon: fas fa-cloud url: https://secureanycloud.com/ - title: GitHub description: Code hosting icon: fab fa-github url: https://github.com/ - name: This server icon: fas fa-server items: - title: Dashy health description: The Dashy server on this instance icon: fas fa-heartbeat url: http://127.0.0.1:4000/healthz YAML
sudo chown dashy:dashy /opt/dashy/user-data/conf.yml
sudo chmod 0640 /opt/dashy/user-data/conf.yml
sudo grep -c 'title:' /opt/dashy/user-data/conf.yml4Check that Dashy serves the new configuration and that a status check works. The password is read from the credentials file and passed to curl on standard input, so it never appears in the process list:
sudo bash -c ' F=/var/lib/sac/data/dashy-credentials.txt P=$(grep "^DASHY_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^DASHY_URL=" $F | sed -E "s#^DASHY_URL=https://([^/]+)/.*#\1#") auth() { printf "user = \"admin:%s\"\n" "$P"; } auth | curl -K - -sk --resolve "$H:443:127.0.0.1" "https://$H/conf.yml" | grep -E "title: (My Services|sac|GitHub)" auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -H "Sec-Fetch-Site: same-origin" \
"https://$H/status-check?url=https%3A%2F%2Fgithub.com%2F" | grep -o "\"successStatus\":true" 'title: My Services - title: sac - title: GitHub "successStatus":trueReload the page in your browser. Each item now shows its status dot:
Status checks and the CORS proxy run from the instance, so they reach whatever the instance can reach. That means hosts in your VPC through its security groups and routes, and the internet through its route table. They can never reach the instance metadata service (169.254.169.254).
Check the security posture from the command line
Without a login, Dashy is refused, including its configuration file under an encoded name. Port 80 redirects to HTTPS, and the health check stays open for load balancers. A request for a host name the instance does not serve is dropped without an answer:
H=$(sudo grep '^DASHY_URL=' /var/lib/sac/data/dashy-credentials.txt | sed -E 's#^DASHY_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'conf.yml, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/conf.yml"
curl -sk -o /dev/null -w 'conf.yml encoded, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/%63onf.yml"
curl -sk -o /dev/null -w 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || truehttp:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ no login -> HTTP 401 conf.yml, no login -> HTTP 401 conf.yml encoded, no login -> HTTP 401 health -> HTTP 200 unknown host -> 000000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.
With the password, Dashy is served. Its status check endpoint answers Dashy's own pages. It refuses, with HTTP 403, a request that a browser marks as coming from another site or that names another site as its origin, so another web page cannot use your signed-in browser to make the server fetch things. A status check aimed at the instance metadata service fails:
sudo bash -c ' F=/var/lib/sac/data/dashy-credentials.txt P=$(grep "^DASHY_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^DASHY_URL=" $F | sed -E "s#^DASHY_URL=https://([^/]+)/.*#\1#") auth() { printf "user = \"admin:%s\"\n" "$P"; } R="--resolve $H:443:127.0.0.1" auth | curl -K - -sk $R -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/" auth | curl -K - -sk $R "https://$H/" | grep -o "<title>[^<]*</title>" auth | curl -K - -sk $R -o /dev/null -w "status-check, same origin -> HTTP %{http_code}\n" \
-H "Sec-Fetch-Site: same-origin" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz" auth | curl -K - -sk $R -o /dev/null -w "status-check, cross-site -> HTTP %{http_code}\n" \
-H "Sec-Fetch-Site: cross-site" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz" auth | curl -K - -sk $R -o /dev/null -w "status-check, other Origin -> HTTP %{http_code}\n" \
-H "Origin: https://evil.example" "https://$H/status-check?url=http%3A%2F%2F127.0.0.1%3A4000%2Fhealthz" auth | curl -K - -sk $R -H "Sec-Fetch-Site: same-origin" \
"https://$H/status-check?url=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F" | grep -o "\"successStatus\":false" 'with login -> HTTP 200 <title>Dashy</title> status-check, same origin -> HTTP 200 status-check, cross-site -> HTTP 403 status-check, other Origin -> HTTP 403 "successStatus":falsenginx keeps only a bcrypt hash of the password. The plain text is in the root-only credentials file:
sudo sed -E 's/\$2y\$12\$.*/$2y$12$<bcrypt-hash>/' /etc/nginx/.dashy-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.dashy-htpasswd /var/lib/sac/data/dashy-credentials.txtadmin:$2y$12$<bcrypt-hash> 640 root:www-data /etc/nginx/.dashy-htpasswd 600 root:root /var/lib/sac/data/dashy-credentials.txtThe certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^DASHY_URL=' /var/lib/sac/data/dashy-credentials.txt | sed -E 's#^DASHY_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltNamesubject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhostDashy and nginx cannot reach the instance metadata service. Dashy runs as its own user with only its configuration directory writable. Neither service starts until first boot has finished:
systemctl show dashy nginx -p IPAddressDeny
systemctl show dashy -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges
systemctl cat dashy nginx | grep -E '^ExecStartPre='IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 User=dashy ReadWritePaths=/opt/dashy/user-data ProtectSystem=strict NoNewPrivileges=yes ExecStartPre=+/usr/bin/test -f /var/lib/sac/data/dashy-firstboot.done ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' ExecStartPre=/usr/bin/test -f /var/lib/sac/data/dashy-firstboot.done ExecStartPre=/usr/bin/test -s /etc/nginx/.dashy-htpasswdYour own DNS name and certificate
Point a DNS record at the instance, then set the name Dashy is served as. The command mints a new self-signed certificate for the name and updates nginx, the cross-site protection and the URL in the credentials file. The name persists across reboots:
sudo dashy-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running dashy-set-hostname again replaces it with a self-signed one:
sudo install -m 0644 <your-domain>.crt /etc/ssl/dashy/dashy.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/dashy/dashy.key
sudo nginx -t && sudo systemctl reload nginxChange the password or add users
The login is an nginx Basic Auth file. To set a new password for admin, pass it to htpasswd on standard input, then reload nginx. printf is a shell builtin, so the password never appears in the process list. Start the line with a space so most shells keep it out of your history:
printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.dashy-htpasswd admin
sudo systemctl reload nginxRunning sudo htpasswd -B -C 12 /etc/nginx/.dashy-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login. Use sudo htpasswd -D /etc/nginx/.dashy-htpasswd <user> to remove one.
Then update or delete DASHY_PASSWORD in /var/lib/sac/data/dashy-credentials.txt, so the file does not hold an old password.
nginx checks the login and does not pass it on to Dashy, so Dashy's own optional login (appConfig.auth) is not needed. Leave it unset.
Server components
Dashy (built app, server, production packages, licence): /opt/dashy
Dashboard configuration (and its backups): /opt/dashy/user-data/
Node.js 24 LTS runtime (no npm, no yarn): /opt/node
Build record, dependency audit, default configuration: /usr/share/doc/sac-dashy/
Dashy service: dashy.service (user dashy, 127.0.0.1:4000)
nginx site: /etc/nginx/sites-available/sac-dashy
Basic Auth file: /etc/nginx/.dashy-htpasswd (bcrypt)
Certificate and key: /etc/ssl/dashy/
First-boot script: /usr/local/sbin/dashy-firstboot.sh
Host name helper: /usr/local/sbin/dashy-set-hostname
Credentials: /var/lib/sac/data/dashy-credentials.txt (mode 0600)
cat /opt/dashy/VERSION /opt/node/bin/node --version ls /opt/node/bin command -v npm yarn || echo "no npm or yarn" nginx -v 2>&1dashy_version=4.9.0 dashy_commit=93a33d945c8b84e5dd37c8bef103770d4701d175 node_version=v24.22.1 v24.22.1 node no npm or yarn nginx version: nginx/1.24.0 (Ubuntu)/usr/share/doc/sac-dashy/BUILD-INFO.txt records the exact source, Node.js and dependency versions. npm-audit-production.json holds the npm advisory audit of every shipped package (0 advisories at build time).
To return to the starting dashboard, copy /usr/share/doc/sac-dashy/conf.yml.default over /opt/dashy/user-data/conf.yml.
When Dashy starts, it checks GitHub for a newer Dashy release and only notes the result in its log (journalctl -u dashy). The browser loads the dashboard's Font Awesome icons from Font Awesome's CDN, as upstream does.
Security recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the per-instance password secret, and rotate it if it may have been shared (Step 10).
- Use your own DNS name and a certificate your clients trust (Step 9).
- Status checks and the CORS proxy can reach whatever the instance can reach in your VPC. Give the instance a security group and subnet that match what you want the dashboard to see.
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Dashy is open source under the MIT licence (/opt/dashy/LICENSE). There is no per-user or per-CPU licence fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
Deploy on AWS
Launch Dashy 4.9.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com