yum upgrades for production use, this is the repository for you.
Active subscription is required.
📅 Updated: March 9, 2026 (Originally published: November 10, 2018)
The NGINX JPEG filter module applies watermarks, logos, and visual effects to JPEG images during request processing — without permanently modifying source files. Rather than batch-processing thousands of images through an external tool, this module operates directly in the JPEG DCT (Discrete Cosine Transform) domain, making overlay operations as lossless as possible. Only the areas where the overlay is applied change; the rest of the image remains untouched.
This approach is particularly valuable for photographers, e-commerce platforms, and content-heavy sites that need dynamic image branding without maintaining duplicate image sets.
Why Use the NGINX JPEG Filter Module?
Traditional watermarking workflows require either pre-processing all images or running a backend application to manipulate them on demand. Both approaches have drawbacks:
- Pre-processing means re-running the entire pipeline whenever you update a logo. For a product catalog with thousands of images, this is both time-consuming and storage-intensive.
- Backend processing (e.g., PHP with GD or ImageMagick) adds latency and CPU load to your application server.
The NGINX JPEG filter module eliminates both problems. It processes images at the web server level as a response filter, meaning:
- Zero application changes — your backend serves original images; NGINX applies overlays transparently.
- Instant logo updates — change the dropon file and reload NGINX; all images immediately reflect the new logo.
- Lossless quality — DCT-domain operations avoid the decode-encode cycle that degrades JPEG quality.
- Conditional processing — combine with NGINX variables to apply watermarks only for unauthenticated users or specific locations.
Practical Use Cases
- Photography galleries: Protect images with a watermark that can be updated without re-processing the entire archive.
- E-commerce stores: Brand product images with your company logo across all listings, sourced from a single overlay file.
- Subscription services: Apply a visible watermark for free-tier users and serve clean images for paying subscribers.
- User-generated content: Automatically stamp uploads with the uploader’s avatar or a site logo.
Installing the NGINX JPEG Filter Module
RHEL, CentOS, AlmaLinux, Rocky Linux
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-jpeg
On older systems using yum (CentOS/RHEL 7):
sudo yum install https://extras.getpagespeed.com/release-latest.rpm
sudo yum install nginx-module-jpeg
Then load the module by adding the following directive at the top of /etc/nginx/nginx.conf, before the events block:
load_module modules/ngx_http_jpeg_filter_module.so;
Reload NGINX to activate:
sudo nginx -t && sudo systemctl reload nginx
Note: This module is currently available for x86_64 architecture only. It requires the
libmodjpeglibrary, which is automatically installed as a dependency.
For additional package details, see the nginx-module-jpeg RPM page.
Verify Installation
After loading the module, confirm it is active by running:
sudo nginx -t
If you see “syntax is ok” and “test is successful,” the NGINX JPEG filter module is loaded and ready for configuration.
NGINX JPEG Filter Module Directives Reference
The NGINX JPEG filter module provides 12 directives. The following table summarizes each directive, followed by detailed explanations.
| Directive | Default | Context | Purpose |
|---|---|---|---|
jpeg_filter |
off |
location |
Enable/disable the module |
jpeg_filter_max_pixel |
0 |
http, server, location |
Limit by total pixel count |
jpeg_filter_buffer |
2M |
http, server, location |
Limit by file size |
jpeg_filter_graceful |
off |
http, server, location |
Serve unmodified on limit exceed |
jpeg_filter_optimize |
off |
http, server, location |
Optimize Huffman tables |
jpeg_filter_progressive |
off |
http, server, location |
Enable progressive encoding |
jpeg_filter_effect |
— | location |
Apply visual effect |
jpeg_filter_dropon_align |
center center |
location |
Position the overlay |
jpeg_filter_dropon_offset |
0 0 |
location |
Offset from alignment |
jpeg_filter_dropon_file |
— | location |
Load overlay from file |
jpeg_filter_dropon_memory |
— | location |
Load overlay from variable |
jpeg_filter
Syntax: jpeg_filter on | off;
Default: off
Context: location
Enables or disables the NGINX JPEG filter module for a given location. When set to off, JPEG images are served without any processing. This directive must be present and set to on for any other jpeg_filter_* directives to take effect.
jpeg_filter_max_pixel
Syntax: jpeg_filter_max_pixel pixel;
Default: 0
Context: http, server, location
Sets the maximum number of pixels (width × height) an image may have for the filter to process it. If the image exceeds this limit, the NGINX JPEG filter module returns a 415 Unsupported Media Type response — unless jpeg_filter_graceful is enabled.
Set to 0 to disable the pixel limit entirely.
# Process images up to 12 megapixels (e.g., 4000x3000)
jpeg_filter_max_pixel 12000000;
jpeg_filter_buffer
Syntax: jpeg_filter_buffer size;
Default: 2M
Context: http, server, location
Controls the maximum file size the NGINX JPEG filter module will process. Images larger than this buffer return 415 Unsupported Media Type unless jpeg_filter_graceful is on.
Increase this value for high-resolution photography or product images:
# Allow images up to 10 MB
jpeg_filter_buffer 10M;
jpeg_filter_graceful
Syntax: jpeg_filter_graceful on | off;
Default: off
Context: http, server, location
When enabled, images exceeding the jpeg_filter_max_pixel or jpeg_filter_buffer limits are served unmodified instead of returning a 415 error. This is strongly recommended for production use — it prevents broken images when users upload unexpectedly large files.
jpeg_filter_graceful on;
jpeg_filter_optimize
Syntax: jpeg_filter_optimize on | off;
Default: off
Context: http, server, location
Optimizes the Huffman coding tables in the output JPEG. This can reduce file size by 2–5% with minimal processing overhead. Recommended for production environments where bandwidth savings matter.
jpeg_filter_optimize on;
jpeg_filter_progressive
Syntax: jpeg_filter_progressive on | off;
Default: off
Context: http, server, location
Converts the output JPEG to progressive encoding. Progressive JPEGs render a low-quality preview first and progressively refine it, improving the perceived loading speed for large images. This is beneficial for image-heavy pages where users scroll through galleries.
jpeg_filter_progressive on;
jpeg_filter_effect
Syntax: jpeg_filter_effect grayscale | pixelate;
Syntax: jpeg_filter_effect darken | brighten value;
Syntax: jpeg_filter_effect tintblue | tintyellow | tintred | tintgreen value;
Default: —
Context: location
Applies a visual effect to the image. All effects except pixelate operate on the YCbCr color space and therefore only apply to images encoded in that space.
Available effects:
grayscale— removes all color components, converting to black and white.pixelate— pixelates the image in 8×8 blocks by zeroing AC coefficients. Useful for obscuring image content while preserving the overall shape.darken value— decreases brightness by reducing DC coefficients in the Y (luminance) component.brighten value— increases brightness by raising DC coefficients in the Y component.tintblue value— shifts the Cb component up, adding a blue tint.tintyellow value— shifts the Cb component down, adding a yellow tint.tintred value— shifts the Cr component up, adding a red tint.tintgreen value— shifts the Cr component down, adding a green tint.
All parameters support NGINX variables, enabling dynamic effect selection.
# Pixelate preview images for non-subscribers
location /preview/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_effect pixelate;
}
jpeg_filter_dropon_align
Syntax: jpeg_filter_dropon_align [top | center | bottom] [left | center | right];
Default: center center
Context: location
Sets the alignment position for the overlay image (called a “dropon” in this module). The first parameter controls vertical alignment, and the second controls horizontal alignment.
Important: This directive must appear before jpeg_filter_dropon_file or jpeg_filter_dropon_memory in the configuration for the alignment to take effect.
All parameters support NGINX variables:
# Position logo in the bottom-right corner
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset
Syntax: jpeg_filter_dropon_offset vertical horizontal;
Default: 0 0
Context: location
Offsets the overlay position from the alignment anchor set by jpeg_filter_dropon_align. Use negative values to move the overlay toward the image center (inward from edges), and positive values to move it outward.
Important: Like jpeg_filter_dropon_align, this directive must appear before the dropon directive it applies to.
All parameters support NGINX variables.
# Place logo 15 pixels from the bottom-right corner
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -15 -15;
jpeg_filter_dropon_file
Syntax: jpeg_filter_dropon_file image;
Syntax: jpeg_filter_dropon_file image mask;
Default: —
Context: location
Applies an overlay from a file path. The overlay image can be a JPEG or PNG file. When using a JPEG overlay, you can optionally specify a separate JPEG mask file where black means fully transparent and white means fully opaque.
If the overlay is a PNG, any alpha channel in the PNG is used for transparency and the mask parameter is ignored.
All parameters support NGINX variables. When no variables are used, the dropon is loaded once during configuration parsing and kept in memory — this is the most efficient approach. When variables are present, the dropon is loaded and unloaded for every request.
jpeg_filter_dropon_file /etc/nginx/watermark.png;
PNG overlays require that libmodjpeg was compiled with PNG support (the default for the GetPageSpeed package).
jpeg_filter_dropon_memory
Syntax: jpeg_filter_dropon_memory $image;
Syntax: jpeg_filter_dropon_memory $image $mask;
Default: —
Context: location
Applies an overlay from an NGINX variable containing a JPEG or PNG bytestream. This is particularly useful with scripting modules like lua-nginx-module to generate dynamic overlays at request time.
The dropon is loaded from the variable during request processing and unloaded afterward.
# Use with Lua to generate a dynamic text overlay
set_by_lua_block $logo_data {
-- generate or load overlay image data
return image_bytestream
}
jpeg_filter_dropon_memory $logo_data;
Configuration Examples
Basic Watermarking
The simplest setup applies a logo to all JPEG images in a location:
location /photos/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_buffer 10M;
jpeg_filter_max_pixel 50000000;
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -10 -10;
jpeg_filter_dropon_file /etc/nginx/logo.png;
}
Conditional Watermarking for Non-Subscribers
Apply a watermark only when the user is not authenticated. This leverages NGINX’s map directive to toggle the filter location:
map $cookie_subscriber $image_location {
"active" /images-clean/;
default /images-watermarked/;
}
server {
listen 80;
location /images/ {
# Internally redirect based on subscription status
rewrite ^/images/(.*)$ $image_location$1 last;
}
location /images-clean/ {
internal;
alias /var/www/images/;
}
location /images-watermarked/ {
internal;
alias /var/www/images/;
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_buffer 10M;
jpeg_filter_dropon_align center center;
jpeg_filter_dropon_file /etc/nginx/watermark.png;
}
}
Image Effects for Previews
Use the pixelate effect to obscure premium content for non-paying users:
location /premium-preview/ {
alias /var/www/premium/;
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_effect pixelate;
}
Grayscale Conversion
Convert images to grayscale on-the-fly — useful for “coming soon” sections or memorial pages:
location /archive/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_effect grayscale;
}
Effect and Overlay Ordering
The order of jpeg_filter_effect and jpeg_filter_dropon_* directives in the configuration matters. They are applied sequentially:
# Effect first, then dropon — the logo stays in color on a grayscale image
location /styled-v1/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_effect grayscale;
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -10 -10;
jpeg_filter_dropon_file /etc/nginx/logo.png;
}
# Dropon first, then effect — both the image and logo become grayscale
location /styled-v2/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -10 -10;
jpeg_filter_dropon_file /etc/nginx/logo.png;
jpeg_filter_effect grayscale;
}
This ordering behavior gives you precise control over the final visual result.
Optimized Production Configuration
For production deployments, enable Huffman optimization and progressive encoding to reduce bandwidth:
location /product-images/ {
jpeg_filter on;
jpeg_filter_graceful on;
jpeg_filter_buffer 15M;
jpeg_filter_max_pixel 100000000;
jpeg_filter_optimize on;
jpeg_filter_progressive on;
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -15 -15;
jpeg_filter_dropon_file /etc/nginx/brand-logo.png;
}
Performance Considerations
The NGINX JPEG filter module processes images in the DCT domain, which is significantly faster than full pixel-domain manipulation. However, there are factors to keep in mind:
- Buffer size directly impacts memory usage. Each request that triggers the filter allocates up to
jpeg_filter_bufferbytes. For a busy server handling many concurrent image requests, set this value carefully. A 10 MB buffer with 100 concurrent image requests means up to 1 GB of memory. - Static dropon files are loaded once. When
jpeg_filter_dropon_fileuses literal paths (no variables), the overlay is loaded into memory at configuration time and shared across all worker processes. This is the most efficient approach. - Variable-based dropons load per request. Using NGINX variables in
jpeg_filter_dropon_fileorjpeg_filter_dropon_memorycauses the overlay to be loaded and unloaded for every request. Use this only when dynamic overlays are truly needed. jpeg_filter_optimizeadds minimal overhead. Huffman table optimization is a lightweight operation that typically saves 2–5% file size — a worthwhile trade-off for production use.- Consider caching. The module processes images on every request. For frequently accessed images, place a caching layer in front — either NGINX’s built-in proxy_cache or a CDN — to avoid redundant processing.
Security Best Practices
When deploying the NGINX JPEG filter module, consider these security guidelines:
- Always enable
jpeg_filter_gracefulin production. Without it, oversized images return 415 errors, which could be exploited to probe your server configuration. - Set reasonable limits. Configure
jpeg_filter_max_pixelandjpeg_filter_bufferto reject excessively large images early, preventing memory exhaustion attacks. - Restrict overlay file permissions. Ensure dropon files are readable by the NGINX worker process but not writable by application users. An attacker who can replace your watermark file could remove branding protection.
- Avoid user-controlled variables in dropon paths. Never pass user input directly into
jpeg_filter_dropon_file. An attacker could use path traversal to load arbitrary files as overlays.
Known Issues
The jpeg_filter_arithmetric Directive
The module includes a jpeg_filter_arithmetric directive for arithmetic JPEG encoding. However, due to a bug in the upstream source code, this directive cannot be used — NGINX reports it as “duplicate” even when specified only once. The field is not properly initialized during configuration creation.
Additionally, arithmetic encoding is not supported by most web browsers, so this limitation has no practical impact on typical deployments.
Platform Availability
The nginx-module-jpeg package is currently available for x86_64 architecture only on RHEL-based distributions (CentOS/RHEL 7–9, AlmaLinux, Rocky Linux). It is not yet available for ARM (aarch64) or Debian/Ubuntu systems.
Troubleshooting
Module Not Loading
If nginx -t reports “unknown directive jpeg_filter,” the module is not loaded. Verify that load_module modules/ngx_http_jpeg_filter_module.so; appears at the top of nginx.conf, outside any block.
415 Unsupported Media Type
This response means the image exceeded either jpeg_filter_max_pixel or jpeg_filter_buffer limits. Either increase the limits or enable jpeg_filter_graceful on to serve the original image instead.
Images Served Unmodified
If images appear without the expected watermark or effect:
- Confirm
jpeg_filter on;is set in the correct location block. - Verify the dropon file exists and is readable by the NGINX worker user.
- Check that the request actually matches the location block where the filter is configured.
- Ensure the served file is actually a JPEG — the module only processes JPEG images and passes all other content types through unmodified.
Dropon Not Positioned Correctly
Remember that jpeg_filter_dropon_align and jpeg_filter_dropon_offset must appear before jpeg_filter_dropon_file in the configuration. If placed after, they have no effect on the dropon.
Validate Your Configuration
Always validate your NGINX configuration after making changes:
sudo nginx -t
You can also use the NGINX Config Validator tool to check for common configuration mistakes.
Conclusion
The NGINX JPEG filter module brings image watermarking and visual effects directly into the web server layer, eliminating the need for external image processing pipelines. By operating in the DCT domain, it preserves JPEG quality while applying overlays, making it ideal for photography sites, e-commerce platforms, and subscription-based content delivery. Install it from the GetPageSpeed repository to start applying dynamic watermarks and effects without modifying a single source image.
