Reverse proxy & HTTPS
Put Caddy or Nginx in front of ImgYoung and get IMGYOUNG_TRUSTED_PROXIES, base_url and the upload size limit right.
Why a reverse proxy
ImgYoung listens on plain HTTP and does not terminate TLS. When serving the public internet, put a reverse proxy in front of it:
- the proxy obtains certificates and serves HTTPS;
- whether the session cookie gets
Secure, and which scheme returned links use whenbase_urlis not set, both come from the proxy'sX-Forwarded-Proto; - the proxy blocks
/metricsat the edge so metrics stay on the internal network; - ImgYoung itself can then listen on loopback only.
Caddy
Caddy obtains and renews certificates automatically, sets X-Forwarded-For / X-Forwarded-Proto / X-Forwarded-Host by default, and forwards Host unchanged.
# Caddy 反向代理示例:Caddy 自动申请并续期 HTTPS 证书。把域名换成你的,DNS 指向本机。
# Caddy 默认就会设置 X-Forwarded-For / -Proto / -Host,并忽略客户端自带的这些头。
# ImgYoung 直接运行在本机(非容器)时,Caddy 的连接来自回环地址,默认受信任,无需额外配置。
# ImgYoung 在容器里时,不论 Caddy 在宿主机还是另一个容器,ImgYoung 看到的对端都是 Docker 网络里的地址,
# 要给 ImgYoung 设 IMGYOUNG_TRUSTED_PROXIES 为该网络的网段(docker network inspect 查看);
# Caddy 也在容器里时,把 reverse_proxy 改成 imgyoung:8721。
img.example.com {
# 指标只给内网抓取,公网入口一律不转发
respond /metrics 404
reverse_proxy 127.0.0.1:8721
}
Nginx
# Nginx 反向代理示例:放进 http {} 块。域名与证书路径换成你的。
# ImgYoung 直接运行在本机(非容器)时,Nginx 的连接来自回环地址,默认受信任,无需额外配置。
# 其余情况要给 ImgYoung 设 IMGYOUNG_TRUSTED_PROXIES 为它看到的对端所在网段:ImgYoung 在容器里时,
# 不论 Nginx 在宿主机还是另一个容器,对端都是 Docker 网络里的地址(docker network inspect 查看);
# Nginx 在别的主机上时,是 Nginx 的地址。
server {
listen 80;
server_name img.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on; # nginx 1.25.1 起的写法;更早的版本改成 listen 443 ssl http2;
server_name img.example.com;
ssl_certificate /etc/nginx/certs/img.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/img.example.com/privkey.pem;
# 不小于后台「单图上限」(出厂 50MB),多出的部分留给 multipart 表单开销;Nginx 默认只有 1MB
client_max_body_size 60m;
# 指标只给内网抓取,公网入口一律不转发
location = /metrics {
return 404;
}
location / {
proxy_pass http://127.0.0.1:8721;
proxy_set_header Host $host;
# ImgYoung 从 X-Forwarded-For 自右向左取第一个非信任代理的地址作客户端地址,
# 登录限速与 /metrics 放行都按它判定;少了这一行,所有请求都会被当成来自 Nginx 本身
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 决定会话 cookie 是否带 Secure,以及未配置 base_url 时返回链接的协议与域名
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
None of the proxy_set_header lines in the example are optional:
Host $host: S3 signature verification covers theHostheader, so a proxy that rewrites it breaks S3 clients with signature mismatches;X-Forwarded-For: without it every request looks like it came from Nginx itself; see the next section;X-Forwarded-ProtoandX-Forwarded-Host: ImgYoung reads their first value, so overwrite them withproxy_set_headerinstead of appending your value after whatever the client sent.
Trusted proxies: IMGYOUNG_TRUSTED_PROXIES
ImgYoung derives three things from each request: the client address (the login rate limit key and the /metrics allow check), whether the first hop was HTTPS (the session cookie's Secure flag), and the public host (for returned links when base_url is not set). X-Forwarded-* headers are honored only when the TCP peer address is inside IMGYOUNG_TRUSTED_PROXIES; when the variable is unset, the default 127.0.0.0/8,::1/128 trusts a proxy on the same host only. Setting it to an empty string (or only whitespace) trusts no proxy at all, so forwarding headers are always ignored; the Bootstrap configuration card in the admin console then shows Empty: forwarded headers are never trusted.
When the peer is trusted:
- All addresses from every
X-Forwarded-Forheader are collected in order, split on commas; entries that do not parse are skipped. - They are checked from right to left, skipping addresses inside trusted networks. The first address outside them is the client.
- If every hop is inside a trusted network (an internal client), the leftmost one is used; if the header is missing or has no usable address, the peer address is used.
- The first value of
X-Forwarded-Protodecides HTTPS:https(case-insensitive) means secure. - The first value of
X-Forwarded-Hostis used as the public host.
When the peer is not trusted, all X-Forwarded-* headers are ignored: the client is the TCP peer, and HTTPS is judged from the connection itself (always false, since ImgYoung does not terminate TLS).
Why spoofing does not work. Each proxy appends the peer it saw to the end of X-Forwarded-For; a client can only control the leftmost part. A client at 203.0.113.7 sending X-Forwarded-For: 1.2.3.4 arrives through a local Nginx as 1.2.3.4, 203.0.113.7; scanning from the right, the first untrusted address is 203.0.113.7, and the forged 1.2.3.4 is ignored. A request that connects to ImgYoung directly has an untrusted peer, so none of its forwarding headers are believed.
Values by topology:
| Topology | Peer address ImgYoung sees | IMGYOUNG_TRUSTED_PROXIES |
|---|---|---|
| Proxy and ImgYoung (binary or systemd) on the same host | 127.0.0.1 / ::1 | The default |
| ImgYoung in Docker, proxy on the host | The Docker bridge gateway, not loopback | The gateway's network, e.g. 172.16.0.0/12; check the actual gateway with docker network inspect |
| Proxy and ImgYoung as two containers in one Compose project | The proxy container's address on the project network | The project network's subnet |
| Proxy on another host | The proxy host's internal address | That address or its network |
| A CDN in front of the proxy | The proxy; the rightmost X-Forwarded-For hop is a CDN node | The proxy's network plus the CDN's origin-pull ranges, so the client resolves to the real visitor |
What a wrong trust setting looks like
If the peer is outside the trusted networks, every proxied request appears to come from the proxy itself: all visitors share one login rate limit, and if the proxy's address happens to be in IMGYOUNG_METRICS_ALLOW (Docker networks are by default) and the proxy forwards /metrics, anyone can read the metrics. A proxy that does not set X-Forwarded-For has the same effect.
Listen on loopback only
With a proxy in front, do not expose ImgYoung's port directly:
Set IMGYOUNG_LISTEN=127.0.0.1:8721. For systemd, add Environment=IMGYOUNG_LISTEN=127.0.0.1:8721 with sudo systemctl edit imgyoung and restart; see Configuration.
When base_url is required
With base_url (Settings → Runtime settings → Access & links → Public site address) empty, ImgYoung derives the public address from each request: the scheme from the HTTPS check, the host from X-Forwarded-Host when the peer is trusted, otherwise from Host. Set it explicitly when:
- the public domain differs from what reaches ImgYoung: a CDN pulls from a different hostname, the proxy rewrites
Hostwithout sendingX-Forwarded-Host, the proxy is not trusted, and so on. Otherwise theurlreturned by uploads, links copied in the admin console, and the endpoint and link prefix in the S3 client settings dialog are all wrong. - CDN cache purging is enabled: purge jobs run in the background with no request to derive from, so they use
base_urlonly; see CDN. - one-click cloud drive authorization is used: the callback URL must match what is registered with the provider character for character and is built from
base_urlonly; see Cloud drives.
Keep the upload size limit in step with the proxy
ImgYoung's max image size (max_upload_mb, 50 MB out of the box) applies to the file itself, so a file exactly at the limit is accepted; the whole request body may be up to the max image size plus 64 KiB, which leaves room for multipart boundaries and short fields such as exif_policy and ttl. The proxy's body limit should be at least that:
- Nginx allows only 1 MB by default; the example sets
client_max_body_size 60m;. - The Caddy example sets no body limit; if you add
request_bodywithmax_size, keep it at or above the max image size plus 64 KiB.
When you raise the max image size in the admin console, raise the proxy limit too, or large images are rejected with 413 by the proxy before they ever reach ImgYoung.
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.
CDN
Putting a CDN in front of ImgYoung — cache headers, WebP negotiation and Vary, cache keys for on-demand processing, Cloudflare cache purging, and how redirect mode and hotlink protection behave behind a CDN.