Configuration
Every bootstrap setting (environment variable) and runtime setting (admin console), with defaults, valid ranges and when changes take effect, plus how to keep the master key safe.
Two kinds of settings
ImgYoung has exactly two kinds of settings. There is no configuration file, and the only command-line flag is -version (prints version, commit and build time, then exits).
| Bootstrap settings | Runtime settings | |
|---|---|---|
| What | What the process needs before it can start: listen address, data directory, master key, trusted proxies, networks allowed on /metrics | Every other operational parameter |
| Source | Environment variables IMGYOUNG_* only | The settings table in the metadata database; environment variables are not read |
| Changing | Change the variable and restart the process | Edit under Settings in the admin console; takes effect on save, no restart |
| Where to see them | The read-only Bootstrap configuration card at the bottom of Settings → Runtime settings | Settings in the admin console |
The split exists so that "where does this value come from" always has exactly one answer: environment variables only get the process started, and some of them also set trust boundaries; runtime settings have a single writer, the admin console, with no seeding from environment variables and no configuration file.
Runtime settings live in the metadata database, so they are included in metadata snapshots and restored with them. Configure a new instance once in the admin console; settings added by an upgrade are filled in with their factory defaults by the database migration.
Bootstrap settings
| Variable | Default | Format | Purpose |
|---|---|---|---|
IMGYOUNG_LISTEN | :8721 | host:port, e.g. 127.0.0.1:8721 or [::1]:8721; port 0 lets the OS pick one | HTTP listen address. ImgYoung only speaks plain HTTP; HTTPS is the reverse proxy's job |
IMGYOUNG_DATA_DIR | data (relative to the working directory) | Directory path; created with mode 0750 if missing | Data directory: metadata database imgyoung.db, master key file master.key, the built-in local channel's blobs/, and the variant cache variants/ |
IMGYOUNG_MASTER_KEY | Empty (use master.key in the data directory) | 64 hex characters (32 bytes) | Master key. Checked against the master key fingerprint stored in the database at every start; a missing or mismatched key refuses to start. See Master key below |
IMGYOUNG_TRUSTED_PROXIES | 127.0.0.0/8,::1/128 | Comma-separated CIDRs or single IPs; a single IP means a network containing only that address | X-Forwarded-For / X-Forwarded-Proto / X-Forwarded-Host are honored only when the connecting peer is in one of these networks; see Reverse proxy & HTTPS |
IMGYOUNG_METRICS_ALLOW | 127.0.0.0/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7 | Same as above | Clients in these networks may read /metrics; everyone else gets 403. See Monitoring & alerts |
-
An unset variable takes its default; a variable set to an empty string (or whitespace only) is explicitly empty. Surrounding whitespace is trimmed from every value. What empty means:
IMGYOUNG_TRUSTED_PROXIESempty: forwarded headers are never trusted;IMGYOUNG_METRICS_ALLOWempty:/metricsis open to no one, every request gets 403;IMGYOUNG_MASTER_KEYempty: same as unset,master.keyin the data directory is used;IMGYOUNG_LISTENandIMGYOUNG_DATA_DIRdo not accept an empty value; setting either to an empty string fails startup, and the error names the variable. A compose file that says${VAR}while the host has no such variable hands the container exactly that empty string; to get the default, leave the variable unset.
The Bootstrap configuration card in the admin console shows an empty network list as "Empty: forwarded headers are never trusted" or "Empty: open to no one".
-
Both network lists are parsed at startup. An entry that is neither a CIDR nor an IP fails startup, and the error names the variable and the bad entry. The listen address is checked when the port is bound and the master key when it is parsed; invalid values also fail startup.
-
The Docker image sets
IMGYOUNG_DATA_DIR=/data;deploy/imgyoung.servicesets it to/var/lib/imgyoung.
Where to set environment variables for each deployment:
IMGYOUNG_LISTEN=127.0.0.1:8721 IMGYOUNG_DATA_DIR=/srv/imgyoung ./imgyoungRuntime settings
Runtime settings are edited under Settings in the admin console, which only administrators can see. Allow empty referer sits on the Hotlink protection tab; everything else is on the Runtime settings tab, grouped as below.
- Saving submits only the fields you changed. All changes are validated together; if any value is invalid, none of them is applied and the reason is shown.
- Changes apply immediately: background loops re-time themselves with a new interval right away instead of finishing the old one.
- Every applied change writes an audit log entry (action
config.update, with old and new values); the Cloudflare API token is only recorded as set or empty. - The same operations are available as
GET /api/admin/config/fullandPUT /api/admin/config, whose body is a JSON object mapping setting keys to new values; see the API reference. - Every duration setting is capped at 100 years (876,000 hours), converted to its own unit: 36,500 days, 876,000 hours, 52,560,000 minutes, 3,153,600,000 seconds, or 3,153,600,000,000 milliseconds; larger values are rejected on save. The "Valid values" column below lists only the lower bound for these settings.
"Default" below is the factory value; "Valid values" is what the server accepts.
Access & links
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
access_mode | Access mode | proxy | proxy / redirect | proxy: images are read from a channel and served by ImgYoung; a failed read moves on to the next channel immediately. redirect: original-image requests get a 302 to a channel that allows direct links, sent with Cache-Control: no-store; see CDN |
base_url | Public site address | Empty | Site root with scheme, e.g. https://img.example.com; a trailing / is removed on save | Empty means it is derived from each request. Required when the public domain differs from what reaches ImgYoung through a CDN or proxy, for CDN cache purging, and for one-click cloud drive authorization; see Reverse proxy & HTTPS |
min_success | Minimum synced replicas | 1 | ≥ 1 | Channels that must be written synchronously before an upload succeeds; if the healthy channels cannot provide that many, the upload fails with 503 |
encrypt_new_channels | Encrypt new channels by default | Off | On / off | Whether a new channel is encrypted when the form does not choose explicitly; encryption is fixed when the channel is created |
Upload limits
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
max_upload_mb | Max image size | 50 | ≥ 1, MB (1 MB = 1024 × 1024 bytes) | Applies to the image itself: a file exactly at the limit is accepted, anything larger gets 413; a multipart request body is additionally capped at "this limit + 64 KiB". Also caps URL imports, S3 uploads, and the source image read for on-demand processing: after lowering it, older images above the new limit fall back to the original instead of being resized or converted |
max_pixels_mp | Pixel budget | 50 | ≥ 1, megapixels | Images whose width × height exceeds max_pixels_mp × 1,000,000 are rejected (422) |
max_concurrent_uploads | Concurrent uploads | 8 | ≥ 1 | One global limit shared by data-plane uploads, URL imports, admin console uploads and every request on the S3 endpoint; when full, requests get 429 (Retry-After: 1) immediately |
upload_rate_per_min | Upload rate limit | 0 | ≥ 0, per minute | Per user, shared between tokens and admin console uploads; burst equals the per-minute allowance. Excess requests get 429 (Retry-After: 60). 0 disables it |
Lifecycle
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
default_ttl_hours | Default lifetime | 0 | ≥ 0, hours | Used when an upload sets no ttl and the user has no default of their own. 0 means never expires |
ttl_renew_on_access | Renew on access | On | On / off | A public access (GET) to a time-limited image that has not yet expired moves its expiry to the time the access is flushed plus its lifetime. An image past its expiry is unreachable right away and is never renewed |
cold_cleanup_days | Cold image threshold | 90 | ≥ 0, days | Images without public access for this long go to Cleanup for a manual decision; nothing is deleted automatically. 0 disables marking |
delete_grace_minutes | Delete grace period | 0 | ≥ 0, minutes | Wait between a stored image losing its last reference and its channel objects being deleted. The deletion time is fixed when the reference count reaches zero, using the value at that moment; later changes do not affect it. 0 deletes on the next cleanup sweep |
cleanup_sweep_seconds | Cleanup sweep interval | 60 | ≥ 1, seconds | Cadence for expiring references, physical deletion, cold-image marking, and reclaiming idempotency keys and audit log entries |
activity_flush_seconds | Activity flush interval | 60 | ≥ 1, seconds | Interval for batch-writing access counts, last-access times and variant cache access times |
idempotency_key_ttl_hours | Idempotency key retention | 24 | ≥ 1, hours | Upload retries carrying the same Idempotency-Key within this window return the first result; older keys are reclaimed |
On-demand processing
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
process_widths | Width tiers | [100,200,400,800,1600] | Array of positive integers, pixels; may be empty | ?w= accepts only these values, anything else gets 400; empty disallows resizing |
process_formats | Target formats | ["webp","jpeg","png"] | Non-empty subset of webp, jpeg, png | ?format= accepts only these values. With webp included, browsers that support WebP automatically get WebP for JPEG / PNG images |
exif_policy | Default EXIF policy | passthrough | passthrough (keep) / strip (remove metadata) | Default EXIF handling at upload; a single upload can override it with the exif_policy form field; does not apply to S3 uploads, which are stored byte for byte |
process_quality_webp | WebP quality | 80 | 0–100 | |
process_quality_jpeg | JPEG quality | 85 | 1–100 | |
process_webp_method | WebP effort | 4 | 0–6 | Higher is slower and smaller |
process_workers | Processing concurrency | 4 | ≥ 1 | Concurrent processing jobs; time spent waiting for a slot counts toward the timeout |
process_timeout_ms | Processing timeout | 5000 | ≥ 1, milliseconds | Includes queueing; on timeout the original is served with Cache-Control: no-store |
variants_cache_mb | Variant cache limit | 10240 | ≥ 1, MB | Variants are cached in <data dir>/variants/; beyond the limit the least recently accessed are evicted |
Deduplication
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
dedup_scan_seconds | Dedup scan interval | 300 | ≥ 1, seconds | Each round first merges byte-identical images automatically, then scans for similar ones |
dedup_phash_max_distance | Similarity threshold | 10 | ≥ 0, Hamming distance | Images whose perceptual hashes are at most this far apart become Duplicates candidates for a manual decision. 0 disables similarity scanning; identical images are still merged |
Reliability & channels
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
health_check_seconds | Health check interval | 60 | ≥ 1, seconds | Cadence of channel health checks and alert evaluation |
backup_interval_hours | Backup interval | 24 | ≥ 1, hours | Interval between automatic metadata snapshots. At startup, a backup runs right away if any healthy channel lacks a snapshot within this interval. See Backup & restore |
backup_keep | Backups to keep | 7 | ≥ 1 | Snapshots are written to every channel that is healthy and not being cleaned up; after each write, that channel keeps only the newest this many |
usage_report_seconds | Usage report interval | 300 | ≥ 1, seconds | Only controls how often the imgyoung_channel_storage_bytes metric is refreshed; usage alerts are evaluated on the health check interval |
channel_quota_gb | Channel usage alert threshold | 0 | ≥ 0, GB (1 GB = 2³⁰ bytes) | Opens an alert when a channel's stored data (sum of image sizes of its completed replicas) exceeds this; writes are not blocked. 0 disables the alert |
CDN cache purge
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
cdn_cloudflare_zone_id | Cloudflare Zone ID | Empty | Text | |
cdn_cloudflare_api_token | Cloudflare API token | Empty | Text | Needs the Cache Purge permission. Stored encrypted with the master key and never shown by any API, page or log. Leaving the field empty keeps the current value; Clear token removes it |
Purging is enabled only when the Zone ID, the API token and base_url are all set. The badge next to the group title reads Purge enabled or Purge disabled, and the startup log prints the same state. See CDN.
Alerts & audit
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
alert_webhook_url | Alert webhook URL | Empty | Empty, or an http / https URL with a host | Receives a JSON POST when an alert opens or resolves; payload format in Monitoring & alerts. Empty means alerts are recorded but not sent |
audit_retention_days | Audit log retention | 0 | ≥ 0, days | Cleanup sweeps delete audit entries older than this. 0 keeps them forever |
Hotlink protection tab
| Key | UI label | Default | Valid values | Notes |
|---|---|---|---|---|
allow_empty_referer | Allow empty referer | On | On / off | With a non-empty Referer allowlist, whether requests without a Referer (typed into the address bar, download tools) are allowed. With an empty allowlist hotlink protection is off and this has no effect |
The allowlist rules are managed on the same tab and are not part of the settings table.
Master key
The master key is 32 random bytes. It is used to:
- encrypt channel configurations (including each channel's credentials) and the Cloudflare API token before they are stored;
- derive the crypt password and salt of every encrypted channel;
- derive the S3 secret of every upload token (never stored, recomputed on demand).
Upload tokens and login passwords are stored as hashes and do not depend on the master key.
Generation and verification
At startup the master key is taken from IMGYOUNG_MASTER_KEY when it is non-empty, in which case master.key is
neither read nor created; otherwise it is read from master.key in the data directory. The key is then checked
against the master key fingerprint recorded in the database. The fingerprint lives in the single-row table
master_key_fingerprint; it is derived from the master key with HKDF-SHA256 and cannot be reversed into the key.
- No fingerprint in the database yet (a brand-new database, or one that has never recorded a fingerprint): if
neither source has a key,
master.keyis generated in the data directory (64 hex characters plus a newline, mode0600, written to a temporary file and renamed) and a warning is logged telling you to back it up; the key's fingerprint is then written to the database. - A fingerprint is already recorded: the master key must be present and must match. If
IMGYOUNG_MASTER_KEYis unset or empty and there is nomaster.keyin the data directory, or the key does not match the fingerprint, startup is refused with an explanation, and no new key is generated. Only a brand-new database ever gets a generatedmaster.key. - If
master.keyexists but is not valid 64-character hex, startup fails rather than generating a new key: a new key would make every existing ciphertext useless.
To generate a master key yourself:
openssl rand -hex 32When switching from the file to the variable, copy the contents of master.key into the variable verbatim; if you get it wrong, the fingerprint check fails and the process refuses to start.
Keeping it safe
The master key is not in metadata snapshots and not in the metadata export. Back it up separately, somewhere other than the server and the snapshots. Copy it right after the first start:
cp data/master.key /safe/place/imgyoung-master.keyLosing it, or starting with the wrong one
The master key cannot be rotated. Once the database has recorded the fingerprint, both a wrong key and a missing key are stopped at startup:
- A different master key (for example a mistyped
IMGYOUNG_MASTER_KEY): startup is refused and the error tells you to switch back to the original key; with the original key back, the process starts normally. - A missing master key (for example
master.keywas deleted from the data directory, or a${VAR}in the compose file is not set on the host): startup is refused and no new key is generated; put the original key back and the process starts normally.
Actually losing the master key is irreversible: the database can no longer be started, and nothing encrypted with the key can be recovered:
- channel configurations, including each channel's credentials, cannot be decrypted;
- data on encrypted channels, including metadata snapshots stored there, can never be decrypted;
- the Cloudflare API token cannot be decrypted;
- the S3 secret of every upload token can no longer be computed.
Back up master.key first
Metadata snapshots, the export and the images in your channels cannot bring back a lost master key. For disaster recovery, the new machine must have the same master key in place before its first start; after restoring another instance's snapshot, switch to that instance's master key, or the fingerprint does not match and startup is refused. See Backup & restore.
Channel encryption
Store only ciphertext at your provider with rclone crypt — what it protects against, how to turn it on, why it is fixed at creation, where the keys come from, and how to decrypt a channel with stock rclone without ImgYoung.
Reverse proxy & HTTPS
Put Caddy or Nginx in front of ImgYoung and get IMGYOUNG_TRUSTED_PROXIES, base_url and the upload size limit right.