Developer Tools AWS

Fossil 2.28 on AWS User Guide

| Product: Fossil 2.28 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Fossil 2.28 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Apache Subversion (SVN) and CI/CD Tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Fossil is a simple, high reliability, distributed version control system from the creator of SQLite. One self contained program gives a project distributed source control, a web interface with a timeline of every change, a wiki, a ticket based bug tracker, a forum and a chat room. A whole repository, including its wiki and tickets, is one SQLite file, so there is nothing else to install and backing it up is copying one file.

The cloudimg image adds:

  • HTTPS from the first boot. nginx serves Fossil on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and Fossil marks its login cookie Secure.
  • Your own repository and admin password. The image ships with no repository. First boot creates a new, empty one and an admin password unique to your instance, written to /root/fossil-info.txt. The password is handed to Fossil on standard input, never on a command line.
  • Private by default. Visitors who have not signed in get nothing: no timeline, no files, no wiki, no clone and no download. You decide later whether anything should be public (Step 9).
  • Fail closed. Fossil refuses to start until first boot has finished, and so does nginx.
  • Loopback only. Fossil listens on 127.0.0.1:8081 behind nginx, which rate limits requests. The only open ports are 22, 80 and 443.
  • Hardened defaults. Fossil runs as its own unprivileged user in a systemd sandbox that can write only /var/lib/fossil. Fossil and nginx are blocked from the instance metadata service; the AMI requires IMDSv2; requests for a host name the instance does not serve are dropped; cross site requests that would change data are refused; and Fossil builds its links from the configured host name, never from what a client sends.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Fossil 2.28 (the official static Linux x64 build, checksum verified), 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 (the web interface and clone, push and pull) and TCP 80 (redirects to HTTPS) from your own networks only.
  • Fossil 2.x on each workstation that will clone the repository (apt install fossil, brew install fossil, or a download from fossil-scm.org).

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Fossil 2.28 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Fossil is light; a smaller instance is fine for a small team), your key pair and a security group as above. The repository lives under /var/lib/fossil on the root volume, so size the root volume for your project's history.

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=fossil}]'

Step 2: Connect to your instance

Version SSH user
Fossil 2.28 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes a few seconds. Then the three units are active:

systemctl is-active fossil nginx fossil-firstboot
active
active
active

Fossil listens on loopback only; nginx serves ports 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /fossil|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
fossil version
State Local Peer
LISTEN 127.0.0.1:8081 users:(("fossil"))
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::1]:8081 users:(("fossil"))
This is fossil version 2.28 [52445a27f1] 2026-03-11 11:31:46 UTC

Step 4: Get your URL and password

The address Fossil is served as (your instance's public DNS name by default) and the admin user:

sudo grep -E '^FOSSIL_(URL|ADMIN_USER)=' /root/fossil-info.txt
FOSSIL_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
FOSSIL_ADMIN_USER=admin

The admin password, generated on this instance (24 letters and digits), is on the FOSSIL_ADMIN_PASSWORD line of the same file:

sudo grep '^FOSSIL_ADMIN_PASSWORD=' /root/fossil-info.txt

Step 5: Sign in from your browser

Open FOSSIL_URL in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or install your own certificate (Step 8). Because the repository is private, Fossil shows its login page straight away. Enter admin and the password from Step 4.

The Fossil login page asking a named user to sign in before the timeline is shown

Use the address in FOSSIL_URL exactly. Fossil checks that every form you submit comes from that address, so the admin pages refuse changes made through a different name for the same instance. To serve Fossil under another name, set it with fossil-set-hostname (Step 8).

Step 6: Browse the timeline, wiki and tickets

The Timeline is the home of the repository: every check-in, wiki edit, ticket change and forum post in date order, with branch tags and links to each change. A new repository starts with Fossil's initial empty check-in; the example below also shows two check-ins pushed from a clone.

The Fossil timeline listing two Add README.md check-ins above the initial empty check-in on the trunk branch, signed in as admin

The Wiki tab holds pages written in Fossil wiki markup or Markdown, versioned with the code. Tickets is a full bug tracker, and Forum and Chat keep discussion next to the code.

A Markdown wiki page titled Project handbook with Edit, Help and History actions for the signed in admin

Step 7: Clone, commit and push from your workstation

Every clone is a full copy of the repository, its wiki and its tickets. On your workstation, clone over HTTPS as admin (Fossil asks for the password):

fossil clone https://admin@<your-fossil-host>/ myproject.fossil

Because the certificate is self signed, Fossil shows its fingerprint and asks whether to accept it. Compare it with the fingerprint of the certificate on the instance, then answer y (or paste the fingerprint):

sudo openssl x509 -noout -fingerprint -sha256 -in /etc/nginx/ssl/fossil.crt | cut -d= -f2 | tr -d ':' | tr 'A-F' 'a-f'

Then open a working copy, commit and push:

mkdir myproject && cd myproject
fossil open ../myproject.fossil
echo "# My project" > README.md
fossil add README.md
fossil commit -m "Add README.md"

With autosync (Fossil's default), fossil commit pulls before it commits and pushes afterwards; fossil push and fossil pull sync by hand.

Step 8: Your own DNS name and certificate

Point a DNS record at the instance, then set the name Fossil is served as. This mints a new self signed certificate for the name, updates nginx, Fossil's base URL and FOSSIL_URL in /root/fossil-info.txt, and keeps the name across reboots:

sudo fossil-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running fossil-set-hostname again replaces it with a self signed one):

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

Step 9: Users, passwords and public access

Add your team under Admin, Users, Add in the web interface, giving each person their own login and capabilities (the developer capability set, ei, lets someone check in and edit the wiki). The user list of a new repository shows that the nobody category (everyone who has not signed in) and the anonymous login have no capabilities:

The Fossil user list: nobody and anonymous have empty capabilities, developer has ei, reader has kptw, and admin is the only user, with the setup capability s

The same from the command line. fossil-repo runs the fossil command as the Fossil service user, so the repository stays owned by it:

for u in admin nobody anonymous; do echo "$u: '$(sudo fossil-repo user capabilities $u -R /var/lib/fossil/repo.fossil)'"; done
sudo fossil-repo settings localauth -R /var/lib/fossil/repo.fossil
sudo fossil-repo settings self-register -R /var/lib/fossil/repo.fossil
sudo fossil-repo settings access-log -R /var/lib/fossil/repo.fossil
admin: 's'
nobody: ''
anonymous: ''
localauth                (local)     1
self-register            (local)     0
access-log               (local)     1

localauth means requests from the instance itself still need a login, self registration is off, and the access log records every login attempt with the client's real address.

To change the admin password, run fossil-set-password. Without arguments it generates a new random password and records it in /root/fossil-info.txt; with --stdin it reads your own (16 to 64 letters and digits):

sudo fossil-set-password
printf '%s\n' '<your-new-password>' | sudo fossil-set-password --stdin

To publish a project read only (browse, clone and download, but no changes), give the nobody category the read capabilities. Do this only for code you mean to make public:

sudo fossil-repo user capabilities nobody ghjorz -R /var/lib/fossil/repo.fossil

Step 10: Security checks

Port 80 sends browsers to HTTPS, the health endpoint answers without a login (for load balancer checks), and the repository itself does not:

H=$(sudo grep '^FOSSIL_URL=' /root/fossil-info.txt | sed -E 's#^FOSSIL_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -L -o /dev/null -w 'timeline without a login: %{http_code} %{url_effective}\n' --resolve "$H:443:127.0.0.1" "https://$H/timeline"
port 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
ok
timeline without a login: 200 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login?g=timeline

With the admin password (read from the credentials file and sent to curl on stdin, so it never appears on a command line), the admin setup page opens:

H=$(sudo grep '^FOSSIL_URL=' /root/fossil-info.txt | sed -E 's#^FOSSIL_URL=https://([^/]+)/.*#\1#'); J=$(mktemp)
sudo sed -n 's/^FOSSIL_ADMIN_PASSWORD=\(.*\)$/u=admin\&p=\1/p' /root/fossil-info.txt \
  | curl -sk --resolve "$H:443:127.0.0.1" -c "$J" --data @- -o /dev/null "https://$H/login"
curl -sk --resolve "$H:443:127.0.0.1" -b "$J" -o /dev/null -w 'admin /setup: %{http_code}\n' "https://$H/setup"
rm -f "$J"
admin /setup: 200

Fossil takes its base URL from configuration (/etc/fossil/fossil.env, written by fossil-set-hostname), never from the request:

cat /etc/fossil/fossil.env
ps -o args= -C fossil | head -1
FOSSIL_BASEURL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
/usr/local/bin/fossil server --localhost --port 8081 --https --baseurl https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com --max-latency 300 /var/lib/fossil/repo.fossil

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

H=$(sudo grep '^FOSSIL_URL=' /root/fossil-info.txt | sed -E 's#^FOSSIL_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
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:localhost

Fossil runs unprivileged in a sandbox, and neither Fossil nor nginx can reach the instance metadata service:

systemctl show fossil -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
User=fossil
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

nginx passes Fossil the configured host name and the real client address from the connection, never values a client sends, and closes the connection for any host name the instance does not serve:

grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP)' /etc/nginx/sites-available/cloudimg-fossil
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || true
        proxy_set_header Host $fossil_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
unknown host name: 000

(000 means nginx closed the connection without an answer.) nginx also refuses a request that would change data when its Origin is another web site (HTTP 403), in addition to Fossil's own cross site request checks.

Step 11: Backups and upgrades

The whole repository, with its wiki, tickets and forum, is one file:

sudo ls -l /var/lib/fossil/
total 216
-rw-r----- 1 fossil fossil 221184 Oct 19 14:06 repo.fossil

Back it up with an EBS snapshot of the root volume, or copy a consistent backup while Fossil keeps running and send it to Amazon S3:

sudo fossil-repo backup -R /var/lib/fossil/repo.fossil /var/lib/fossil/backup-$(date +%F).fossil

Every clone is also a complete copy, so a workstation clone that you sync regularly is an off instance backup too.

The image ships the Fossil version named in the listing as one static program at /usr/local/bin/fossil. To move to a newer release later, snapshot the instance, replace that file with the new official Linux build, and run sudo systemctl restart fossil.

Step 12: Server components

Component Path
Fossil /usr/local/bin/fossil (licence at /usr/share/doc/fossil/COPYRIGHT-BSD2.txt)
Repository (one SQLite file) /var/lib/fossil/repo.fossil
Base URL /etc/fossil/fossil.env
Service unit /etc/systemd/system/fossil.service
nginx site /etc/nginx/sites-available/cloudimg-fossil
Certificate and key /etc/nginx/ssl/
First boot script /usr/local/sbin/fossil-firstboot.sh
Helpers /usr/local/sbin/fossil-set-hostname, /usr/local/sbin/fossil-set-password, /usr/local/sbin/fossil-repo
Credentials /root/fossil-info.txt (mode 0600)

Step 13: Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Give each person their own Fossil login, with only the capabilities they need, rather than sharing admin.
  • Keep the admin password secret, and rotate it if it may have been shared (Step 9).
  • Use your own DNS name and a certificate your browsers and Fossil clients trust (Step 8).
  • Open anonymous read access only for code you mean to publish (Step 9).
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Back up the repository regularly (Step 11).

Support and licensing

Fossil is open source under the BSD 2-Clause licence (a copy is at /usr/share/doc/fossil/COPYRIGHT-BSD2.txt); there is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA