1. What you need
- A Linux server — Debian 12+, Ubuntu 22.04+, or Fedora 39+; amd64 or arm64 — with systemd and root or sudo access.
- A domain name for the gallery, e.g.
photos.example.com. - Two object-storage buckets, on Backblaze B2, Wasabi, Cloudflare R2, or any S3-compatible service:
- a public bucket, for photos anyone may view;
- a private bucket, for private photos and database backups.
- Optional, recommended: a Cloudflare account, with your domain's DNS on Cloudflare. It enables tunnel mode (no open ports on your server) and edge caching.
Do the steps in order: set up storage (step 3) before running the installer (step 4).
2. Choose how HTTPS works
Pick one TLS mode. You pass it to the installer as PHOTARIUM_TLS.
| Mode | Use it when | What you do before installing |
|---|---|---|
tunnelrecommended with Cloudflare | Your domain's DNS is on Cloudflare. | Create a Cloudflare Tunnel and copy its token (below). No ports need to be open on the server. |
autodefault | No Cloudflare, or you want Photarium reachable directly. | Point the domain's DNS at the server and open ports 80 and 443 to the internet (the installer opens them in ufw/firewalld; cloud firewalls and security groups are up to you). |
proxy | You already run a reverse proxy (nginx, Caddy, …). | Configure your proxy to forward to http://127.0.0.1:8080. |
tunnel: create a Cloudflare Tunnel
- In the Cloudflare dashboard, go to Networking → Tunnels and choose Create a tunnel. (The same tunnels are also listed in Cloudflare One / Zero Trust under Networks → Connectors.)
- Name the tunnel (e.g.
photarium) and create it. - Cloudflare shows an install command for
cloudflaredcontaining a long token starting witheyJ. Copy only the token — that isPHOTARIUM_TUNNEL_TOKEN. You do not need to run the command; the Photarium installer installscloudflareditself. Continue. - On the tunnel's Routes tab, Add route → Published application:
- Subdomain / Domain: your gallery hostname, e.g.
photos+example.com. - Service URL:
http://localhost:8080.
- Subdomain / Domain: your gallery hostname, e.g.
- Save. The server needs outbound access to Cloudflare on port 7844; no inbound ports.
auto: point DNS at the server
Create an A record (and AAAA if the server has IPv6) for your domain, pointing at the server's public IP. If the domain's DNS is on Cloudflare, set the record to DNS only (grey cloud), not Proxied: in auto mode Photarium obtains and renews its own Let's Encrypt certificate and must be reached directly on ports 80 and 443. If you want Cloudflare in front, use tunnel instead.
Check that DNS has propagated before installing:
getent hosts photos.example.com # should print the server's public IP
curl -4 https://icanhazip.com # the server's public IPv4, for comparison
proxy: your own reverse proxy
Photarium listens on 127.0.0.1:8080 over plain HTTP. Your proxy terminates TLS and forwards to it. Allow request bodies of at least 64 MB, or photo uploads fail.
Caddy:
photos.example.com {
reverse_proxy 127.0.0.1:8080
}
nginx (inside your server { … } block for the domain, with TLS configured):
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
If that proxy is itself behind Cloudflare's proxy (orange cloud), also set PHOTARIUM_BEHIND_CLOUDFLARE=yes, and restrict ports 80/443 to Cloudflare's IP ranges.
3. Set up storage
Photarium keeps every photo in object storage: public photos in the public bucket (served straight to visitors), private photos and database backups in the private bucket. Follow the section for your provider; each ends with the exact installer variables to set.
Bucket names below are examples — bucket names are global on most providers, so choose your own.
Backblaze B2
- Sign in at backblaze.com and open B2 Cloud Storage → Buckets.
- Create a Bucket for public photos, e.g.
example-photos-public. Choose Public as its privacy setting. Leave Object Lock off.- Backblaze only allows a first public bucket once your account has a verified email address and payment history on file (or you pay the small fee offered in the dialog, which is credited to your balance).
- Create a Bucket for private photos and backups, e.g.
example-photos-private, with privacy set to Private. - Note the Endpoint shown on either bucket, e.g.
s3.us-west-004.backblazeb2.com. The region is the part betweens3.and.backblazeb2.com— hereus-west-004. (A B2 account lives in one region, so both buckets share it.) - On the public bucket, open Lifecycle Settings and choose Keep only the last version of the file. B2 keeps every old version of a file by default, so without this a deleted photo, or one made private, stays stored in the public bucket as a hidden version. With it, B2 removes hidden versions after about a day. Doing the same on the private bucket stops deleted photos and old backups from accumulating.
- Open Application Keys → Add a New Application Key:
- Name of Key:
photarium-public - Allow access to Bucket(s): the public bucket only
- Type of Access: Read and Write
- Tick Allow List All Bucket Names (Backblaze recommends it for S3-compatible clients)
- Leave the file name prefix and duration empty.
- Create New Key, then copy the keyID and applicationKey. The applicationKey is shown only once.
- Name of Key:
- Repeat step 6 for the private bucket (
photarium-private, private bucket only, Read and Write).
Do not use the Master Application Key: B2's S3-compatible API does not accept it.
Installer variables:
PHOTARIUM_STORAGE=b2
PHOTARIUM_S3_REGION=us-west-004 # from the bucket endpoint
PHOTARIUM_PUBLIC_BUCKET=example-photos-public
PHOTARIUM_PRIVATE_BUCKET=example-photos-private
PHOTARIUM_S3_KEY_ID=<keyID of the public-bucket key>
PHOTARIUM_S3_SECRET=<applicationKey of the public-bucket key>
PHOTARIUM_PRIVATE_S3_KEY_ID=<keyID of the private-bucket key>
PHOTARIUM_PRIVATE_S3_SECRET=<applicationKey of the private-bucket key>
The public URL is derived for you: https://<public-bucket>.s3.<region>.backblazeb2.com.
Cost note: B2 includes free egress up to three times your average monthly storage, and egress to Cloudflare is free (Backblaze pricing page, subject to change).
Wasabi
- Choose a region. Wasabi's S3 endpoint for a region is
s3.<region>.wasabisys.com— for examples3.us-east-1.wasabisys.com(also reachable ass3.wasabisys.com),s3.us-west-1.wasabisys.com,s3.eu-central-1.wasabisys.com,s3.ap-northeast-1.wasabisys.com. The full list is in Wasabi's docs. - In the Wasabi Console, Buckets → Create Bucket. Create the public bucket (e.g.
example-photos-public) in your region. Leave Bucket Versioning and Object Locking off (Object Locking cannot be turned off later). Create the private bucket (e.g.example-photos-private) in the same region. - Make the public bucket publicly readable. Wasabi restricts this:
- Accounts created after March 13, 2023 cannot enable public access themselves in the console or CLI; you must ask Wasabi Support ([email protected]) to enable it.
- Trial accounts cannot use public access at all.
- Paid accounts created before that date can enable it themselves.
Once public access is available on your account, open the public bucket's Settings → Permissions tab (called “Policies” in older docs), choose Edit, paste this policy with your bucket name, and Save:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPublicRead", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::example-photos-public/*" } ] }Wasabi's own example also grants
s3:GetObjectVersion; it is left out here on purpose, so earlier versions of objects can never be read publicly. Never apply a public policy to the private bucket. - Create an access key: Access Keys → Create Access Key, choose Root User or a Sub-User, and Create. Download the CSV or copy both values — the secret key cannot be retrieved later.
Least privilege (recommended): create a policy (Policies → Create Policy) limited to the two buckets, a sub-user (Users → Create User) with that policy attached, and the access key for that sub-user:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "arn:aws:s3:::*" }, { "Effect": "Allow", "Action": "s3:*", "Resource": [ "arn:aws:s3:::example-photos-public", "arn:aws:s3:::example-photos-public/*", "arn:aws:s3:::example-photos-private", "arn:aws:s3:::example-photos-private/*" ] } ] }
Cost notes, from Wasabi's pricing FAQ and docs (check them for current terms):
- Pay-as-you-go storage has a 90-day minimum storage duration: an object deleted sooner is still billed for the remaining days.
- There is a 1 TB minimum monthly charge.
- Egress is free under a fair-use policy: your monthly egress should not exceed the amount of data you store. Photarium serves public photos straight from the public bucket, so a popular gallery can exceed that; Wasabi says use cases that regularly do are not a good fit and may be limited.
Installer variables:
PHOTARIUM_STORAGE=wasabi
PHOTARIUM_S3_REGION=us-east-1 # your buckets' region
PHOTARIUM_PUBLIC_BUCKET=example-photos-public
PHOTARIUM_PRIVATE_BUCKET=example-photos-private
PHOTARIUM_S3_KEY_ID=<access key>
PHOTARIUM_S3_SECRET=<secret key>
The public URL is derived for you: https://s3.<region>.wasabisys.com/<public-bucket>.
Cloudflare R2
- In the Cloudflare dashboard, open R2 object storage and create two buckets, e.g.
example-photos-publicandexample-photos-private(3–63 characters: lowercase letters, digits, hyphens). Leave the location automatic and do not choose a jurisdiction (see the note below). - Give the public bucket public access — open it, then Settings:
- Custom Domains → Add (recommended): enter a hostname on a zone in the same Cloudflare account, e.g.
img.example.com, then Continue → Connect Domain. Cloudflare caches it and applies your zone's security settings. - Or Public Development URL → Enable (type
allowto confirm). This gives anhttps://pub-….r2.devURL that is rate-limited and meant for testing only — not for a real gallery.
- Custom Domains → Add (recommended): enter a hostname on a zone in the same Cloudflare account, e.g.
- Leave the private bucket as it is: R2 buckets are private by default.
- Create S3 credentials: on the R2 object storage page, next to API Tokens (under Account Details), choose Manage, then create a User API token (or an Account API token):
- Permissions: Object Read & Write
- Scope it to the two buckets only.
- Create it, then copy the Access Key ID and Secret Access Key. The secret is shown only once.
- Copy your Account ID, shown under Account Details on the R2 object storage page.
- If you connected a custom domain, create a cache-purge token, so a photo you delete or make private stops being served from Cloudflare's cache immediately.
The installer derives the endpoint, https://<account-id>.r2.cloudflarestorage.com, and the region, auto.
Installer variables:
PHOTARIUM_STORAGE=r2
PHOTARIUM_R2_ACCOUNT_ID=<account id>
PHOTARIUM_PUBLIC_BUCKET=example-photos-public
PHOTARIUM_PRIVATE_BUCKET=example-photos-private
PHOTARIUM_S3_KEY_ID=<Access Key ID>
PHOTARIUM_S3_SECRET=<Secret Access Key>
PHOTARIUM_PUBLIC_URL=https://img.example.com # custom domain or r2.dev URL, no trailing slash
PHOTARIUM_CF_API_TOKEN=<cache purge token> # recommended with a custom domain
PHOTARIUM_CF_ZONE_ID=<zone id of img.example.com>
Jurisdiction note: buckets created in a jurisdiction (EU, FedRAMP) use a different endpoint, https://<account-id>.eu.r2.cloudflarestorage.com (or .fedramp.). The r2 option derives only the default endpoint, so for those use the generic s3 option below with PHOTARIUM_S3_ENDPOINT set to the jurisdiction endpoint.
Other S3-compatible storage
Any service that speaks the S3 API (AWS S3, MinIO, and others) works. You need:
- two buckets: the public one must allow anonymous
s3:GetObject, the private one must not; - an access key with read and write access to both (or one key per bucket, using the
PRIVATE_pair for the private bucket); - the S3 endpoint URL, and the URL where objects in the public bucket can be fetched anonymously.
Installer variables:
PHOTARIUM_STORAGE=s3
PHOTARIUM_S3_ENDPOINT=https://s3.example-provider.com
PHOTARIUM_S3_REGION=<region, if your provider uses one>
PHOTARIUM_PUBLIC_BUCKET=example-photos-public
PHOTARIUM_PRIVATE_BUCKET=example-photos-private
PHOTARIUM_S3_KEY_ID=<access key>
PHOTARIUM_S3_SECRET=<secret key>
PHOTARIUM_PUBLIC_URL=https://example-photos-public.s3.example-provider.com # no trailing slash
4. Install
One-line install
On the server:
curl -fsSL https://dl.photarium.waxquixotic.com/install.sh | sudo bash
It asks for everything it needs — domain, TLS mode, storage provider, keys, buckets — and finishes by printing your gallery's URL and admin credentials.
Unattended install (automation and AI agents)
Pass every answer as an environment variable, placed after sudo, and set PHOTARIUM_NONINTERACTIVE=1 so the installer never prompts and fails clearly if something required is missing. Example for Backblaze B2 behind a Cloudflare Tunnel:
curl -fsSL https://dl.photarium.waxquixotic.com/install.sh | sudo \
PHOTARIUM_NONINTERACTIVE=1 \
PHOTARIUM_DOMAIN=photos.example.com \
PHOTARIUM_TLS=tunnel \
PHOTARIUM_TUNNEL_TOKEN=eyJhIjoi... \
PHOTARIUM_STORAGE=b2 \
PHOTARIUM_S3_REGION=us-west-004 \
PHOTARIUM_PUBLIC_BUCKET=example-photos-public \
PHOTARIUM_PRIVATE_BUCKET=example-photos-private \
PHOTARIUM_S3_KEY_ID=... \
PHOTARIUM_S3_SECRET=... \
PHOTARIUM_PRIVATE_S3_KEY_ID=... \
PHOTARIUM_PRIVATE_S3_SECRET=... \
bash
For another provider, swap in the variables from its section in step 3. For auto mode, drop the two tunnel lines (or set PHOTARIUM_TLS=auto) and optionally add PHOTARIUM_ACME_EMAIL.
Already root? Leave out sudo and put the variables before bash: curl -fsSL … | PHOTARIUM_NONINTERACTIVE=1 PHOTARIUM_DOMAIN=… bash.
Secrets passed this way end up in your shell history. Many shells skip history for commands that start with a space; otherwise clear the entry afterwards.
If PHOTARIUM_ADMIN_PASSWORD is not set, the installer generates one and prints it once, at the end. Save it.
Installer variables
| Variable | Required | Meaning |
|---|---|---|
PHOTARIUM_DOMAIN | yes | Public hostname, e.g. photos.example.com |
PHOTARIUM_TLS | no (default auto) | auto: Photarium gets and renews its own Let's Encrypt certificate (ports 80+443 must reach the server directly; the DNS record must NOT be proxied through Cloudflare). tunnel: Cloudflare Tunnel — no open ports at all, Cloudflare in front (recommended when using Cloudflare). proxy: listen on 127.0.0.1:8080 plain HTTP behind your own reverse proxy. |
PHOTARIUM_TUNNEL_TOKEN | for tunnel | The tunnel token from Cloudflare dashboard → Networking → Tunnels → Create a tunnel (the eyJ… string in the cloudflared install command it shows). |
PHOTARIUM_BEHIND_CLOUDFLARE | no | yes if requests arrive through Cloudflare's proxy (implied by tunnel); makes Photarium trust CF-Connecting-IP for rate limiting and view counts. |
PHOTARIUM_ADMIN_USER | no (default admin) | Admin username. |
PHOTARIUM_ADMIN_PASSWORD | no | Admin password; generated and printed once if omitted. |
PHOTARIUM_STORAGE | yes | b2, wasabi, r2, or s3 (any S3-compatible). |
PHOTARIUM_S3_REGION | b2, wasabi | e.g. us-west-004 (B2), us-east-1 (Wasabi). |
PHOTARIUM_R2_ACCOUNT_ID | r2 | Cloudflare account ID. |
PHOTARIUM_S3_ENDPOINT | s3 | Endpoint URL; for the other providers it is derived automatically. |
PHOTARIUM_S3_KEY_IDPHOTARIUM_S3_SECRET | yes | Access key with read/write on both buckets. |
PHOTARIUM_PRIVATE_S3_KEY_IDPHOTARIUM_PRIVATE_S3_SECRET | no | Separate key for the private bucket (recommended on B2, where keys can be restricted to one bucket). |
PHOTARIUM_PUBLIC_BUCKETPHOTARIUM_PRIVATE_BUCKET | yes | Bucket names. |
PHOTARIUM_PUBLIC_URL | r2, s3 (derived for b2/wasabi) | Base URL that serves the public bucket's objects, no trailing slash. |
PHOTARIUM_CF_API_TOKENPHOTARIUM_CF_ZONE_ID | no | Enables purging Cloudflare's cache when a photo is deleted or made private (only needed if Cloudflare caches your image domain). Token needs Zone → Cache Purge. |
PHOTARIUM_ACME_EMAIL | no | Contact email for Let's Encrypt. |
PHOTARIUM_VERSION | no (default latest) | Install a specific version. |
PHOTARIUM_AUTO_UPDATE | no (default idle) | How updates are applied: idle (automatically when the site is quiet), scheduled (automatically in the hour after 03:00, changeable in Settings), or off (only when the admin clicks Update now). |
PHOTARIUM_NONINTERACTIVE | no | 1 = never prompt; fail if something required is missing. |
When the PRIVATE_ pair is set, PHOTARIUM_S3_KEY_ID / PHOTARIUM_S3_SECRET is used for the public bucket.
What the installer does
- Detects the distribution and CPU architecture, and installs
curl,ca-certificatesandtarif they are missing. - Downloads the release and verifies its SHA-256 checksum.
- Creates a
photariumsystem user. - Installs the programs to
/opt/photarium, writes the configuration to/etc/photarium/photarium.env(mode0640), and keeps data in/var/lib/photarium. - Sets the admin password.
- For
auto: opens ports 80 and 443 in ufw or firewalld. Fortunnel: installscloudflaredfrom Cloudflare's package repository and connects the tunnel. - Tests the storage (
photosctl check): writes, reads and deletes a small test object in each bucket, and fetches it anonymously through the public URL. If anything fails it stops here, before starting the server, and says what failed — fix/etc/photarium/photarium.envand re-run the installer. - Enables and starts two systemd units:
photarium.service(the server) andphotarium-backup.timer(a daily database backup to the private bucket; the 14 most recent are kept). - Waits until the site answers, then prints the URL and the admin credentials.
Re-running the installer upgrades the programs in place and keeps the existing configuration.
When it finishes, open https://<your domain> and sign in with the printed credentials.
5. Updates
Photarium updates itself. About every six hours it checks for a new release, and by default it installs one on its own once the site has been quiet for ten minutes (no uploads or edits in progress). The site restarts for a few seconds. Before every update:
- a snapshot of the database is saved in
/var/lib/photarium/updates/; - the programs being replaced are kept in
/opt/photarium/backup/<version>/.
If the new version doesn't come up healthy, Photarium puts the old one back automatically, and won't try that version again on its own.
Choose how updates happen
Sign in as the admin and open Settings → Software updates:
- Automatically, when the site is quiet (the default);
- Automatically, at a set time — within the hour after the time you pick (server time), e.g. 03:00;
- Only when I click “Update now” — the page tells you when a version is available and installs it when you press the button.
To set the default at install time, use PHOTARIUM_AUTO_UPDATE=idle, scheduled or off (see the installer variables above).
How it stays safe
- Every release is signed. The release key's public half is built into Photarium. Before installing, the server and a separate root helper both check the signature and the archive's checksum, so a tampered download — or a compromised download host — cannot install anything. Downgrades are refused.
- The server never runs as root.
photosddownloads and verifies the update, then asks a small systemd service (photarium-update) to install it. That service checks everything again on its own. - It rolls back. After restarting, the helper waits for the new version to answer health checks; if it doesn't, the previous programs are restored.
Privacy, and turning it off
Checking for updates fetches one small file from dl.photarium.waxquixotic.com. Only your server's IP address and a photosd/<version> user agent reach it — nothing about your gallery.
In /etc/photarium/photarium.env (then sudo systemctl restart photarium):
UPDATE_CHECK=false— never look for updates;SELF_UPDATE=false— look, and tell the admin, but never install on its own.
Updating by hand
Re-run the installer. It replaces the programs, keeps /etc/photarium/photarium.env and your data, and restarts the service:
curl -fsSL https://dl.photarium.waxquixotic.com/install.sh | sudo bash
Unattended:
curl -fsSL https://dl.photarium.waxquixotic.com/install.sh | sudo PHOTARIUM_NONINTERACTIVE=1 bash
To move to a specific version, add PHOTARIUM_VERSION=<version>. The current version number is published at dl.photarium.waxquixotic.com/latest/VERSION.
Going back to the previous version by hand
sudo systemctl stop photarium
ls /opt/photarium/backup/ # one folder per previous version
sudo cp /opt/photarium/backup/<version>/{photosd,photosctl,backupctl} /opt/photarium/
sudo systemctl start photarium
The database snapshot from just before the update is in /var/lib/photarium/updates/; restore it (with the service stopped) only if the newer version had changed the database in a way the older one can't read.
6. Uninstall
There is no uninstall script; these commands remove Photarium but keep your data.
sudo systemctl disable --now photarium.service photarium-backup.timer photarium-update.path
for u in photarium.service photarium-backup.service photarium-backup.timer photarium-update.service photarium-update.path; do
f=$(systemctl show -P FragmentPath "$u"); [ -n "$f" ] && sudo rm -f "$f"
done
sudo systemctl daemon-reload
sudo rm -rf /opt/photarium /etc/photarium
If you used tunnel mode, also remove the tunnel connector, and delete the tunnel in the Cloudflare dashboard:
sudo cloudflared service uninstall
Your database and local data in /var/lib/photarium, and everything in your buckets, are kept. Delete them only if you are sure — this cannot be undone:
sudo rm -rf /var/lib/photarium
sudo userdel photarium
The buckets and their contents you delete in your storage provider's console.
7. Cloudflare recommendations
Setting names below were checked against Cloudflare's documentation in September 2026; Cloudflare renames and moves settings from time to time.
Use tunnel mode
PHOTARIUM_TLS=tunnel is the recommended setup with Cloudflare. cloudflared makes an outbound connection to Cloudflare, so the server has no open inbound ports: nobody can reach Photarium except through Cloudflare, where your WAF, rate-limiting and cache rules apply.
Site hostname (e.g. photos.example.com)
- SSL/TLS encryption mode — Full (strict) (SSL/TLS → Overview). Applies to
proxymode behind Cloudflare: your reverse proxy must present a valid certificate for the hostname (a free Cloudflare Origin CA certificate works). If the zone shows Automatic SSL/TLS, choose Custom SSL/TLS to set Full (strict) explicitly. Not applicable intunnelmode — traffic between Cloudflare andcloudflaredis encrypted independently of this setting — or inautomode, where the record is DNS only and Cloudflare is not in the path. - Always Use HTTPS — On (SSL/TLS → Edge Certificates). Redirects every HTTP request to HTTPS. It applies to every proxied hostname in the zone.
- HSTS — with caution (SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS)). Tells browsers to use only HTTPS for your domain. Start with a short Max Age (1 month), leave includeSubDomains and Preload off unless every subdomain serves HTTPS, and raise it later. While it is active, do not switch the record to DNS only or pause Cloudflare, or visitors may be locked out.
- Minimum TLS Version — TLS 1.2 (SSL/TLS → Edge Certificates). Refuses outdated TLS 1.0/1.1 clients. (It does not apply to R2 custom domains.)
- WAF managed rules (Security → Settings, “Web application exploits”). The Cloudflare Free Managed Ruleset is on by default on the Free plan. On Pro and above, turn on the Cloudflare Managed Ruleset as well.
- Bot Fight Mode — usually leave off (Security → Settings, “Bot traffic”). It can challenge API and automated traffic, and it cannot be bypassed with WAF custom rules. That can break scripts or uptime monitors that call your gallery, and challenge crawlers that are not on Cloudflare's verified-bot list — the very crawlers Photarium's SEO features are built for. Turn it on only if bots are a real problem, then test signing in and uploading.
- Do not cache
/api/*(Caching → Cache Rules → Create rule). Filter: URI Path starts with/api/— expressionstarts_with(http.request.uri.path, "/api/")— then Cache eligibility: Bypass cache, and Deploy. API responses are per-user: signed-in admin data and private photos are served through/api/, and must never be stored in a shared edge cache. This rule also protects you from any broader “cache everything” rule added later. - Compression is automatic. Cloudflare retired the Brotli toggle and compresses responses automatically (Brotli or Zstandard, depending on plan). Nothing to set.
Image hostname (a Cloudflare-cached public bucket, e.g. img.example.com)
This applies when Cloudflare serves your public bucket — an R2 custom domain.
- Cache everything with a long edge TTL (Caching → Cache Rules): filter Hostname equals
img.example.com; Cache eligibility: Eligible for cache; Edge TTL: Ignore cache-control header and use this TTL — e.g. 1 month when a purge token is configured (next item), or 1 day without one. Keep Browser TTL modest (e.g. a few hours): Cloudflare can purge its own cache, but not visitors' browsers. - Tiered Cache (Caching → Tiered Cache → Smart Tiered Cache). Free on all plans. Cloudflare data centers fetch from a nearby upper tier instead of each going to your bucket, so fewer requests reach storage. Cloudflare recommends it for R2.
- Cache Reserve — optional (Caching → Cache Reserve). A paid, usage-billed add-on that keeps cached photos in persistent storage so rarely viewed ones stay cached. Worth considering for large libraries.
- Set a purge token — this is about privacy. When you delete a photo or make it private, Photarium removes it from the public bucket, but copies already cached by Cloudflare would keep being served until they expire. With a token, Photarium purges them immediately.
- My Profile → API Tokens → Create Token → Create Custom Token.
- Permissions: Zone → Cache Purge → Purge.
- Zone Resources: Include → Specific zone → the image domain's zone.
- Create it and copy the token:
PHOTARIUM_CF_API_TOKEN. - The zone ID is on the zone's Overview page, in the API section:
PHOTARIUM_CF_ZONE_ID.
Rate-limit sign-in attempts
Photarium limits login attempts itself; a Cloudflare rule stops password guessing at the edge, before it reaches your server.
Security → Security rules → Create rule → Rate limiting rules:
- If incoming requests match:
http.request.uri.path eq "/api/auth/login" - With the same characteristics: IP
- When rate exceeds: e.g. 5 requests per 10 seconds (Free), or 10 per minute (Pro and above)
- Then: Block, for the longest duration your plan allows (10 seconds on Free, up to 1 hour on Pro).
On Business and Enterprise you can narrow it to sign-in submissions only: http.request.uri.path eq "/api/auth/login" and http.request.method eq "POST". The Free and Pro plans cannot match on request method, so use the path-only rule there. (Free allows one rate-limiting rule.)
Proxy without a tunnel: allow only Cloudflare
If you use proxy mode behind Cloudflare's proxy (orange cloud) instead of a tunnel, allow ports 80/443 only from Cloudflare's IP ranges. Otherwise anyone can reach the server directly, around your WAF and rate limits — and because PHOTARIUM_BEHIND_CLOUDFLARE=yes trusts the CF-Connecting-IP header, a direct request could claim any visitor address. With ufw:
# if you opened 80/443 to everyone before, remove those rules first:
sudo ufw delete allow 80/tcp
sudo ufw delete allow 443/tcp
for ip in $(curl -fsSL https://www.cloudflare.com/ips-v4) $(curl -fsSL https://www.cloudflare.com/ips-v6); do
sudo ufw allow proto tcp from "$ip" to any port 80,443
done
Cloudflare's ranges are published at cloudflare.com/ips and change occasionally; re-run the loop now and then. (Tunnel mode needs none of this — nothing is open.)
8. Windows (edge cases)
Photarium is designed for Linux servers.
The Windows build is for testing, evaluation and unusual environments, and is not recommended for production.
- Download
photarium-windows-amd64.zipfrom the download page and verify it. - Unzip it to a folder, e.g.
C:\Photarium. - In the folder that holds
photosd.exe, copyphotarium.env.exampletophotarium.envand fill it in — the comments in the file explain each setting.photosdreadsphotarium.envfrom next to itself automatically. - Set the admin password, then start the server:
cd C:\Photarium
Copy-Item photarium.env.example photarium.env
notepad photarium.env
.\photosctl.exe set-password admin
.\photosd.exe
To run it at boot, use a service wrapper such as NSSM (nssm install Photarium C:\Photarium\photosd.exe, with the startup directory set to C:\Photarium), or a Task Scheduler task triggered At startup that runs photosd.exe with Start in set to its folder and “Run whether user is logged on or not” selected. Schedule backupctl.exe the same way if you want database backups.
9. Troubleshooting
Follow the server's log live, and check whether it is running:
journalctl -u photarium -f
systemctl status photarium
Certificate errors in auto mode
Photarium could not get a Let's Encrypt certificate. Usually one of:
- DNS does not point at the server yet — compare
getent hosts photos.example.comwith the server's public IP, and wait for propagation; - port 80 (or 443) is blocked — check ufw/firewalld and any cloud firewall or security group;
- the DNS record is proxied through Cloudflare (orange cloud). Set it to DNS only, or better, re-run the installer with
PHOTARIUM_TLS=tunnel.
Storage errors
Uploads fail, images are missing, or the log mentions the bucket. Check the keys and bucket names in /etc/photarium/photarium.env, fix them, then restart:
sudo nano /etc/photarium/photarium.env
sudo -u photarium PHOTARIUM_CONFIG=/etc/photarium/photarium.env /opt/photarium/photosctl check
sudo systemctl restart photarium
photosctl check tests each bucket (write, read, delete) and the public URL, and names the step that fails. Common causes: a B2 key restricted to the other bucket, the B2 Master Application Key (not supported), the wrong region, or — on Wasabi — public access not yet enabled on the account, which leaves public images unreadable.
Tunnel mode: site unreachable
Check the connector and its log, and that the route's Service URL is http://localhost:8080:
systemctl status cloudflared
journalctl -u cloudflared -f
Backups
See when the backup last ran and what it said:
systemctl list-timers photarium-backup.timer
journalctl -u photarium-backup
Photarium is free software under the GNU Affero General Public License v3.0. Copyright © 2026 Scott Alan Miller and NTG LLC | Quixotic Systems. Source code.