PiGallery2 3.5.2 on AWS User Guide
Overview
This guide covers launching and using the PiGallery2 3.5.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg File Sharing and Sync listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
PiGallery2 is an open source, self hosted photo and video gallery. It takes a folder of photos and videos, keeps your folder structure, indexes the files into a small SQLite database, makes thumbnails and serves a fast web gallery with search, a map of geotagged photos, faces, albums and password protected sharing links. Your photos stay on your own instance.
The cloudimg image adds:
- HTTPS and a unique admin password from the first boot. PiGallery2 itself listens on 127.0.0.1 only. nginx serves it on port 443 with a certificate generated on your instance; port 80 redirects to HTTPS, and requests for a host name the instance does not serve get no answer. PiGallery2's own login is on, and the
adminpassword is generated on first boot and written to/root/pigallery2-credentials.txt(readable by root only). PiGallery2 stores only a bcrypt hash of it. The upstream fallback loginadmin/adminis never created, and nothing secret is baked into the image. - The empty password login fix. In PiGallery2 3.5.2, a login with a known user name and an empty password is accepted. The PiGallery2 developers fixed this in their code in May 2026, but no release contains the fix yet. This image applies that fix (upstream commit bf910bed3) to 3.5.2, and its self test proves that empty and malformed passwords are refused.
- A current runtime and refreshed dependencies. PiGallery2 runs on Node.js 22.23.3 LTS, with image processing by sharp 0.35.5 (libvips 8.18) and video thumbnails by Ubuntu's ffmpeg (kept current by apt). The libraries PiGallery2 3.5.2 pinned carried known high and critical vulnerabilities. They are updated here, and the finished tree is scanned against the OSV vulnerability database: no known advisories at build time. The build record is in
/usr/share/doc/pigallery2/BUILD-PROVENANCE.txt. - Hardened defaults. PiGallery2 runs as its own unprivileged user under a locked down systemd unit. PiGallery2 and nginx are blocked from the instance metadata service, and the AMI requires IMDSv2. Client addresses come from the connection, not from headers a client can forge. Repeated sign in attempts are rate limited, and a browser request that changes something and comes from another web site is refused. Extensions (third party server code) and browser uploads are off until you turn them on.
- A dedicated data volume. Your photos, the index, the thumbnails and PiGallery2's configuration live on their own 20 GiB EBS volume mounted at
/var/lib/pigallery2, which you can grow on its own. - No sample content. The gallery starts empty; you add your own photos.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: PiGallery2 3.5.2 (/opt/pigallery2/app), Node.js 22.23.3 (/opt/pigallery2/node), nginx, ffmpeg, 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 443 (the gallery) and TCP 80 (redirects to HTTPS) from the people who will use it, and TCP 22 (SSH, also how you copy photos in) from your own networks only.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick PiGallery2 3.5.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (making thumbnails for a large library is CPU heavy; a bigger type indexes faster), your key pair and a security group as above.
The AMI has two volumes: the 8 GiB root volume and a 20 GiB data volume for /var/lib/pigallery2. Size the data volume for your library plus roughly a fifth again for thumbnails (for example 200 GiB instead of 20 GiB); the first boot grows its file system to fill the volume.
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 \
--block-device-mappings 'DeviceName=/dev/sdf,Ebs={VolumeSize=200,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=pigallery2}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| PiGallery2 3.5.2 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 PiGallery2, nginx and the first boot unit are active:
systemctl is-active pigallery2 nginx pigallery2-firstboot
active
active
active
nginx is the only web listener; PiGallery2 itself (a Node.js process) listens on 127.0.0.1:8080 only:
sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|:8080 /' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 127.0.0.1:8080 users:(("node"))
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"))
The runtime, the PiGallery2 release and the data volume:
/opt/pigallery2/node/bin/node --version
jq -r .version /opt/pigallery2/app/package.json
findmnt -no SOURCE,FSTYPE,SIZE /var/lib/pigallery2
v22.23.3
3.5.2
/dev/nvme1n1 ext4 19.5G
Step 4: Get your URL and password
sudo cat /root/pigallery2-credentials.txt
The file holds the address PiGallery2 is served as (your instance's public IP address by default) and the admin password generated on this instance:
# PiGallery2 3.5.2 - generated on first boot by pigallery2-firstboot.service. Unique to this instance; keep it safe.
PIGALLERY2_URL=https://<public-ip>/
PIGALLERY2_USER=admin
PIGALLERY2_PASS=<24 letters and digits>
# Sign in to the gallery with this user name and password. Only a bcrypt hash of the password is stored by PiGallery2.
# Add photos and videos: copy them into /var/lib/pigallery2/images (the 'ubuntu' user can write there), then open the
# gallery (folders are indexed when you browse them; Settings -> Indexing rescans everything).
# Change the host name: sudo pigallery2-set-host <dns-name>
# Change the password: sudo pigallery2-set-password (prompts)
Step 5: Add your photos and videos
PiGallery2 reads photos and videos from /var/lib/pigallery2/images, and its folders become albums in the gallery. The ubuntu user is in the pigallery2 group, which can write there, so you can copy photos straight in over SSH. From your own computer, for example:
rsync -av --progress -e "ssh -i <your-key.pem>" ~/Pictures/Your-Photos/ ubuntu@<public-ip>:/var/lib/pigallery2/images/
(scp -r, SFTP clients and aws s3 sync run on the instance work too.) Keep the hidden file .cloudimg-gallery-root in the folder: PiGallery2 will not index an empty photo folder, which protects its index if the storage is missing. The file keeps a new gallery working and is never shown.
PiGallery2 indexes a folder the first time someone opens it, and notices new and removed files when the folder is opened again. To index the whole library at once (recommended after a large copy), sign in, open Settings from the menu at the top right, switch the mode (top right) to Advanced and choose Run now: Indexing in the Indexing section. Thumbnails are made as you browse; the Photo and Video sections have jobs that make them ahead of time.
The photo folder and its group permissions:
ls -ld /var/lib/pigallery2/images
id -nG ubuntu
drwxrwsr-x 5 pigallery2 pigallery2 4096 Oct 20 20:26 /var/lib/pigallery2/images
ubuntu adm cdrom sudo dip lxd pigallery2
Step 6: Sign in
Open PIGALLERY2_URL in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or use your own name and certificate (Step 10). Sign in as admin with PIGALLERY2_PASS:

The gallery opens on your photo folder: sub folders first, then the photos in it, grouped by date:

The images in these screenshots are generated test patterns and fractals; the image ships with an empty gallery.
Step 7: Browse, search and share
Open a folder to see its photos and videos as a grid; the bar at the top shows where you are, how many items there are, and lets you download, filter, sort and group them:

Click a photo to open it full screen; the arrows move between items, the play button starts a slideshow and the i button shows its details (file, size, date and, when the photo has them, camera, lens and location):

The search box finds photos by file name, folder, date, place, camera and keywords. Share creates a link to the current folder for people without a login, optionally with a password and an expiry; a share link uses your configured address (Step 10). Albums saves searches, Faces lists the people tagged in your photos' metadata, and the map shows geotagged photos (the map tiles are loaded by your browser from OpenStreetMap).
Step 8: Settings
Signed in as admin, open Settings from the menu at the top right. Basic mode shows the main options; Advanced shows everything else, including indexing, thumbnail and video conversion jobs and the user list (Add user), where you can give other people their own logins and roles:

PiGallery2 keeps its settings in /var/lib/pigallery2/config/config.json (readable by the pigallery2 user only; it holds the session signing keys and the admin password hash). The settings that make up this image's security model:
sudo python3 -c 'import json; c=json.load(open("/var/lib/pigallery2/config/config.json")); print(json.dumps({"Server":{k:c["Server"][k] for k in ("host","port","publicUrl","trustProxy")},"Users":{"authenticationRequired":c["Users"]["authenticationRequired"]},"Extensions":{"enabled":c["Extensions"]["enabled"]},"Upload":{"enabled":c["Upload"]["enabled"]}},indent=2))'
{
"Server": {
"host": "127.0.0.1",
"port": 8080,
"publicUrl": "https://<public-ip>",
"trustProxy": "loopback"
},
"Users": {
"authenticationRequired": true
},
"Extensions": {
"enabled": false
},
"Upload": {
"enabled": false
}
}
Keep Host 127.0.0.1, Port 8080, Trust Proxy loopback and Password protection on: PiGallery2 must only be reachable through nginx, and with password protection off anyone who can reach the instance gets full admin rights. Two features are off on this image:
- Upload lets signed in users upload photos through the browser. nginx accepts uploads of up to 1 GiB per request.
- Extensions download and run third party server code from a repository on the internet, and extensions with dependencies need npm, which this image does not ship. Turn them on only for extensions you trust.
Step 9: Check the security model
The image ships a self test that proves the login over HTTPS: the gallery refuses you without signing in, refuses a wrong password, an empty password and admin / admin, accepts the password from your credentials file, and PiGallery2 listens on 127.0.0.1 only:
sudo /usr/local/sbin/pigallery2-selftest
OK: https://<public-ip>/ refuses no session / wrong / empty / admin:admin, logs in with the per-instance password, app on 127.0.0.1:8080 only
Plain HTTP redirects to HTTPS, the gallery's API refuses you until you sign in, a sign in with an empty password is rejected, a sign in posted from another web site is refused, and a request for a host name the instance does not serve gets no answer:
H=$(sudo sed -nE 's#^PIGALLERY2_URL=https://([^/]+)/.*#\1#p' /root/pigallery2-credentials.txt)
curl -s -o /dev/null -w 'plain HTTP: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'gallery API without signing in: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/pgapi/gallery/content/"
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' -H "Origin: https://$H" --data '{"loginCredential":{"username":"admin","password":""}}' "https://$H/pgapi/user/login" | cut -c1-80
curl -sk -o /dev/null -w 'sign in posted from another web site: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -H 'Content-Type: application/json' --data '{}' "https://$H/pgapi/user/login"
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/ || true
plain HTTP: 301 -> https://<public-ip>/
gallery API without signing in: 401
{"error":{"code":50,"message":"not all parameters are included for loginCredenti
sign in posted from another web site: 403
unknown host name: 000
PiGallery2 and nginx cannot reach the instance metadata service; nginx hands PiGallery2 the configured host and overwrites the client address with the address of the connection; the secrets are readable by root (or PiGallery2, for its own configuration) only:
systemctl show pigallery2 nginx -p Id -p IPAddressDeny
grep -E 'proxy_set_header (Host|X-Real-IP|X-Forwarded-For) ' /etc/nginx/pigallery2-proxy.conf
sudo stat -c '%a %U:%G %n' /root/pigallery2-credentials.txt /var/lib/pigallery2/config/config.json /etc/nginx/ssl/pigallery2.key
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=pigallery2.service
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=nginx.service
proxy_set_header Host $pg_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
600 root:root /root/pigallery2-credentials.txt
600 pigallery2:pigallery2 /var/lib/pigallery2/config/config.json
600 root:root /etc/nginx/ssl/pigallery2.key
The certificate was generated on this instance and names its own addresses:
H=$(sudo sed -nE 's#^PIGALLERY2_URL=https://([^/]+)/.*#\1#p' /root/pigallery2-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = <public-ip>, O = cloudimg
X509v3 Subject Alternative Name:
IP Address:<public-ip>, DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
Step 10: Your own DNS name and certificate
Point a DNS record at the instance, then set the name PiGallery2 is served as. This mints a new self signed certificate for the name, updates nginx, PiGallery2's public URL (used in share links) and the URL in /root/pigallery2-credentials.txt, restarts PiGallery2 and keeps the name across reboots:
sudo pigallery2-set-host <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running pigallery2-set-host again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/pigallery2.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/pigallery2.key
sudo nginx -t && sudo systemctl reload nginx
Step 11: Change the password
Run the helper on the instance. It prompts for the new password twice (so it never appears on a command line), stores it as a bcrypt hash in PiGallery2's database and configuration, updates PIGALLERY2_PASS in /root/pigallery2-credentials.txt and restarts PiGallery2:
sudo pigallery2-set-password
The helper also resets a forgotten password. To give other people their own logins, use Add user under Settings (Advanced mode, Users); each user signs in with their own password and role.
Step 12: Storage, backups and server components
To grow the data volume, modify the EBS volume in the console or CLI, then grow the file system on the instance (it is mounted by UUID with nofail):
sudo resize2fs "$(findmnt -no SOURCE /var/lib/pigallery2)"
Your originals are the photo folder. The index, thumbnails and converted videos are rebuilt by PiGallery2 when missing; your albums, shares and users live in the database. Back up /var/lib/pigallery2/images, /var/lib/pigallery2/db and /var/lib/pigallery2/config (or snapshot the data volume).
| Component | Path |
|---|---|
| PiGallery2 3.5.2 | /opt/pigallery2/app |
| Node.js 22.23.3 runtime | /opt/pigallery2/node/bin/node |
| Data volume | /var/lib/pigallery2 (20 GiB gp3, ext4) |
| Your photos and videos | /var/lib/pigallery2/images |
| Configuration | /var/lib/pigallery2/config/config.json (pigallery2, 0600) |
| Index database (SQLite) | /var/lib/pigallery2/db |
| Thumbnails and converted videos | /var/lib/pigallery2/tmp |
| systemd unit | pigallery2.service (user pigallery2, 127.0.0.1:8080) |
| nginx site | /etc/nginx/sites-available/cloudimg-pigallery2 |
| Certificate and key | /etc/nginx/ssl/ |
| First boot script | /usr/local/sbin/pigallery2-firstboot.sh |
| Host name helper | /usr/local/sbin/pigallery2-set-host |
| Password helper | /usr/local/sbin/pigallery2-set-password |
| Self test | /usr/local/sbin/pigallery2-selftest |
| Credentials | /root/pigallery2-credentials.txt (mode 0600) |
| Licences, build record, dependency scan | /usr/share/doc/pigallery2/ |
Step 13: Security recommendations
- Allow port 22 in the security group from your own networks only; open 80 and 443 only to the people who should see the gallery.
- Keep the admin password private and change it if it may have leaked (Step 11). Give other people their own users rather than the admin login.
- Use your own DNS name and a certificate your users trust (Step 10), so nobody learns to click through certificate warnings.
- Give share links a password and an expiry when they leave your organisation.
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else (including ffmpeg updates).
Support and licensing
PiGallery2 is free software under the MIT licence (Copyright bpatrik); a copy is at /usr/share/doc/pigallery2/LICENSE, and Node.js's licence at /usr/share/doc/pigallery2/NODE-LICENSE. This image runs the PiGallery2 3.5.2 release from https://github.com/bpatrik/pigallery2 with the runtime, dependency and security changes listed in /usr/share/doc/pigallery2/BUILD-PROVENANCE.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