A Next-Generation Image Proxy & Origin Server in Rust
Serving JPEG XL (.jxl), AVIF (.avif), WebP (.webp), and Legacy Formats on the Fly with 100% In-Process Rust Codecs, Smart Content Negotiation, and Cache Acceleration.
JXLify is a high-performance image proxy and origin web server written in Rust, designed as a modern, memory-safe, asynchronous evolution of webp_server_go.
JXLify sits as a middle-man in the loop between your users/CDN and your raw image assets. When a client requests an image (e.g. https://example.com/photos/landscape.jpg), JXLify dynamically negotiates client support using HTTP Accept headers and User-Agent heuristics, converts the image on the fly to the optimal format (JPEG XL, AVIF, or WebP), caches the generated asset and its metadata on disk, and serves it with instant response times and minimal CPU usage.
JXLify negotiates image formats using a 4-tier hierarchy:
Instead of converting all formats simultaneously on every request (which wastes CPU and increases latency), JXLify determines the single highest-priority format the client supports and converts/serves only that format:
- Client supports JPEG XL (
image/jxl): Serves cached JXL or encodes to JXL. - Client supports AVIF (
image/avif): Serves cached AVIF or encodes to AVIF. - Client supports WebP (
image/webp): Serves cached WebP or encodes to WebP. - Client supports none of the above: Serves the original raw image (or resized raw image).
If the source raw image is already in the negotiated format (e.g. source is already photo.webp and client accepts WebP) and no resizing/cropping is requested, JXLify skips conversion entirely and serves the file directly with 0ms CPU overhead.
All data is cleanly organized under the data/ directory:
data/pics: Original source images.data/cache: Unified disk cache directory (safe to delete at any time):data/cache/images/: Converted.jxl,.avif,.webpand resized variants (2-level sharded).data/cache/metadata/: JSON metadata & BlurHash strings (2-level sharded).data/cache/remote/: Downloaded upstream images in reverse proxy mode (2-level sharded).
In config.toml, only two paths are needed:
img_path = "./data/pics" # Source images (local path or remote URL)
cache_path = "./data/cache" # Unified cache (safe to delete anytime)| Config Key | Purpose | Typical Value | Description |
|---|---|---|---|
img_path |
Original Image Source | "./data/pics" or "https://cdn.example.com" |
Directory (or upstream URL) containing raw source images. |
cache_path |
Unified Disk Cache | "./data/cache" |
Cache directory for all generated data (images/, metadata/, remote/). Safe to delete at any time (rm -rf ./data/cache). |
If your original image files are hosted on the local server or mounted volume:
- Put your source images into
./data/pics/(or any path like/var/www/uploads/). - Set
img_path = "./data/pics". - Set
cache_path = "./data/cache". - Requests to
http://localhost:3333/photos/cat.jpgwill resolve to./data/pics/photos/cat.jpgand cache to./data/cache/images/....
If you want JXLify to sit in front of an existing remote image server:
- Set
img_path = "https://origin.example.com/assets". - Requests to
http://localhost:3333/photos/cat.jpgwill fetchhttps://origin.example.com/assets/photos/cat.jpg. - JXLify caches the raw file in
data/cache/remote/, converts it to the client's optimal format, caches the result indata/cache/images/, and serves it instantly.
In high-volume production environments with millions of images, flat directories cause severe filesystem inode lock contention and slow directory lookups. JXLify automatically implements 2-level hash sharding:
data/cache/images/local/
βββ 8f/
β βββ 32/
β βββ 8f32d4639f7f415f.jxl
β βββ 8f32d4639f7f415f.avif
β βββ 8f32d4639f7f415f.webp
βββ 01/
β βββ ee/
β βββ 01eef898821f07c6.webp
This distributes millions of files evenly across
No external CLI tools (FFmpeg / ImageMagick) are required. JXLify processes all formats entirely in-process:
- JPEG XL: In-process via
jpegxl-rs(libjxl) andjxl-encoder. Preserves full alpha transparency (RGBA8/RGBA16). - AVIF: Pure Rust via
ravif/rav1e(the engine behindcavif-rs). Preserves full alpha transparency. - WebP: In-process via
webpandimage/image-webp. Preserves alpha transparency. - JPEG / PNG / BMP / GIF: Pure Rust decoding and encoding via
image.
- Decoding: Multi-frame GIFs are parsed in-process via
image::codecs::gif::GifDecoder, extracting all frames, transparent palettes, and frame delays. - Encoding: Converted directly to animated WebP in-process using
webp-animation, preserving frame rates, loop counts, and alpha transparency. - Pass-through: Original animated GIF/WebP files are served untouched for legacy clients.
- π Asynchronous & Multi-Threaded: Built on Axum and Tokio with asynchronous I/O and Rayon thread pool for fast encoding.
- π― Intelligent Content Negotiation: Evaluates
Acceptheader MIME types andUser-Agentheuristics (e.g. Safari 17+, iOS 17+, Firefox >= 93, Chrome). - π On-The-Fly Conversion: Automatic conversion of
jpg,png,gif,bmp,svg,heic,nefintojxl,avif, orwebp. - ποΈ Persistent Disk Caching with Hash Sharding: Caches optimized images and metadata in
CACHE_PATHwith 2-level directory sharding. - π In-Flight Deduplication Lock: Uses async lock registry (
DashMap) to eliminate duplicate encoding when multiple concurrent requests hit the same uncached image. - π Local Origin & Remote Proxy Modes: Can serve from local directory (
IMG_PATH: "./data/pics") or act as a reverse proxy for remote CDNs (IMG_PATH: "https://origin.example.com"). - π On-Demand Resizing & Smart Cropping: Supports query parameters
?width=300&height=200,?max_width=800&max_height=600with multiple crop algorithms. - π Image Metadata Endpoint: Append
?meta=fullto retrieve image dimensions, color profile, size, and BlurHash string. - π§Ή Automatic Cache Cleaner: Periodically enforces
MAX_CACHE_SIZEusing LRU / modification time pruning. - π¦ Prefetching: Multi-threaded batch scanner (
--prefetch/--prefetch-foreground) to pre-warm the cache before production deployment. - π HTTP Caching & Metrics: Sends
Vary: Accept, User-Agent, weakETag,Cache-Control, andX-Compression-Rate. - π©Ί Health Check: Built-in
/healthzendpoint for Kubernetes and load balancer monitoring.
You can install the jxlify server binary directly from crates.io:
cargo install jxlifyOnce installed, verify the installation by running:
jxlify --helpAdd jxlify to your project's Cargo.toml:
[dependencies]
jxlify = "0.1"Or add it via the command line:
cargo add jxlifygit clone https://github.com/fokx/jxlify.git
cd jxlify
cargo build --releaseThe compiled binary will be located at target/release/jxlify.
Generate a default config.toml:
./target/release/jxlify --dump-config > config.toml| Option | Type | Default | Description | Environment Override |
|---|---|---|---|---|
host |
String |
"0.0.0.0" |
IP address to bind (use 127.0.0.1 for localhost only, 0.0.0.0 for all interfaces). |
JXLIFY_HOST |
port |
String/Int |
"3333" |
TCP port for the HTTP server. | JXLIFY_PORT |
quality |
Integer |
80 |
Compression quality (1β100). 80 offers visually lossless quality; 100 triggers true lossless encoding for JXL and WebP. |
JXLIFY_QUALITY |
allowed_types |
Array |
["jpg", "png", ...] |
Whitelist of allowed image extensions. Requests for other extensions return 400 Bad Request. Use ["*"] to allow all. |
JXLIFY_ALLOWED_TYPES |
convert_types |
Array |
["jxl", "avif", "webp"] |
Enabled modern output formats. Order does not matter (fallback priority is always JXL -> AVIF -> WebP). Remove a format (e.g. ["webp"]) to disable it. |
JXLIFY_CONVERT_TYPES |
strip_metadata |
Boolean |
true |
When true, strips EXIF, GPS coordinates, and camera profiles from output files to minimize size and protect privacy. |
JXLIFY_STRIP_METADATA |
img_path |
String |
"./data/pics" |
Root directory or remote HTTP/HTTPS upstream URL for source images. | JXLIFY_IMG_PATH |
cache_path |
String |
"./data/cache" |
Unified cache directory for all generated data (images/, metadata/, remote/). |
JXLIFY_CACHE_PATH |
enable_extra_params |
Boolean |
false |
When true, enables on-demand dynamic resizing via query parameters (?width=, ?height=, ?max_width=, ?max_height=). |
JXLIFY_ENABLE_EXTRA_PARAMS |
crop_interesting |
String |
"InterestingAttention" |
Focal point algorithm for aspect-ratio crops. Options: InterestingAttention, InterestingEntropy, InterestingCentre, InterestingNone. |
JXLIFY_EXTRA_PARAMS_CROP_INTERESTING |
cache_ttl |
Integer |
2592000 |
Browser cache TTL in seconds (default 2592000 = 30 days). Sent in Cache-Control: max-age=.... |
JXLIFY_CACHE_TTL |
max_cache_size |
Integer |
0 |
Maximum disk cache size in Megabytes (e.g. 10240 for 10 GB). 0 = unlimited. Background cleaner automatically removes oldest LRU files when exceeded. |
JXLIFY_MAX_CACHE_SIZE |
read_buffer_size |
Integer |
4096 |
Internal I/O read buffer size in bytes for streaming files. | JXLIFY_READ_BUFFER_SIZE |
concurrency |
Integer |
262144 |
Maximum concurrent request worker pool size. | JXLIFY_CONCURRENCY |
disable_keepalive |
Boolean |
false |
Set to true to close TCP connection after each request (Connection: close). |
JXLIFY_DISABLE_KEEPALIVE |
- Controls lossy compression density across all encoders (1β100).
- 80 is the recommended default, providing ~70β85% file size reduction with zero visible artifacting.
- 100 activates lossless compression mode in libjxl (JPEG XL) and libwebp (WebP).
allowed_types: Security control preventing arbitrary file serving. Only extensions in this list will be processed (e.g.jpg,jpeg,png,gif,bmp,svg,heic,nef,webp,avif,jxl).convert_types: Allows selectively disabling newer formats if desired. For example, settingconvert_types = ["avif", "webp"]disables JXL conversion even if client supports it.
When enabled, JXLify dynamically resizes images based on URL query parameters:
GET /photo.jpg?width=400&height=300: Resizes and crops to exact 400x300 dimensions using the configured smart crop algorithm (crop_interesting).GET /photo.jpg?max_width=800&max_height=600: Proportionally fits image within bounding box while preserving original aspect ratio.
- If
max_cache_size = 10240(10 GB), a lightweight background cleaner runs every 60 seconds. - If total cache size exceeds the limit, the cleaner automatically purges the oldest, least-recently-modified files (LRU policy) until cache size is within limits.
- Also cleans stale
.tmp.*temporary files from interrupted writes.
# Start server with default ./config.toml
./target/release/jxlify
# Specify custom config file
./target/release/jxlify --config /etc/jxlify/config.toml
# Prefetch all images in background while server runs
./target/release/jxlify --prefetch --jobs 8
# Prefetch all images in foreground and exit
./target/release/jxlify --prefetch-foreground --jobs 8
# Print version
./target/release/jxlify -VA production-ready systemd unit file is provided at jxlify.service.
# 1. Install binary to /usr/bin/
sudo install -Dm755 target/release/jxlify /usr/bin/jxlify
# 2. Install default configuration to /etc/jxlify/
sudo install -Dm644 config.toml /etc/jxlify/config.toml
# 3. Install systemd service unit
sudo install -Dm644 jxlify.service /etc/systemd/system/jxlify.service
# 4. Reload systemd daemon
sudo systemctl daemon-reload# Start and enable JXLify on boot
sudo systemctl enable --now jxlify
# Check service status
sudo systemctl status jxlify# Stream live logs
journalctl -u jxlify -f# Request image (Safari 17+ will receive image/jxl, Chrome will receive image/avif, older browsers get image/webp or JPEG)
curl -i -H "Accept: image/jxl,image/avif,image/webp,image/*,*/*;q=0.8" http://localhost:3333/sample.jpg
# Request image with on-the-fly resizing
curl -i http://localhost:3333/sample.jpg?width=400&height=300
# Inspect metadata & BlurHash
curl -i http://localhost:3333/sample.jpg?meta=fullHTTP/1.1 200 OK
Content-Type: image/jxl
Content-Length: 42150
Vary: Accept, User-Agent
ETag: W/"a9b8c7d6e5"
Cache-Control: public, max-age=31536000, immutable
X-Original-Format: jpeg
X-Served-Format: jxl
X-Compression-Rate: 0.38
Server: JXLifyLicensed under the Apache License, Version 2.0 (LICENSE).