Site icon GetPageSpeed

NGINX JPEG Filter Module: Watermarks On-the-Fly

NGINX JPEG Filter Module: Add Watermarks and Overlays On-the-Fly

📅 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:

The NGINX JPEG filter module eliminates both problems. It processes images at the web server level as a response filter, meaning:

Practical Use Cases

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 libmodjpeg library, 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:

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:

Security Best Practices

When deploying the NGINX JPEG filter module, consider these security guidelines:

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:

  1. Confirm jpeg_filter on; is set in the correct location block.
  2. Verify the dropon file exists and is readable by the NGINX worker user.
  3. Check that the request actually matches the location block where the filter is configured.
  4. 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.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version