Over a million developers have joined DZone.

NGINX Plus Resizes Images for Responsive Web Design: Part II

Here is Part II about how to use the Image‑Filter module for NGINX and NGINX Plus to deliver responsive images without the headache of creating and managing a myriad of image asset variants.

· Performance Zone

See Gartner’s latest research on the application performance monitoring landscape and how APM suites are becoming more and more critical to the business, brought to you in partnership with AppDynamics.

Did you miss out on Part I? Click the link and check it out!

Considerations for Production Use

The configuration described in Matching Image Size to Pixel Density can deliver any size variant of an image depending on the directory name used to request the image. However, for a production environment we don’t want to wait for the web server to resize images for each request. That is not good for overall latency and can also add significant CPU overhead.

The most effective solution is to cache our resized image variants so that subsequent requests for each variant are served from the cache, without going through the Image‑Filter module. We can achieve this with NGINX Plus configuration by defining a separate server that performs image resizing, and proxy requests to it only if the requested image size is not already in the cache. We call this the responsive image server.

A production configuration with cohosted separate NGINX Plus instances for a frontend web server and a responsive image server. For a more responsive web design the web server caches size variants created by the image server using the Image-Filter module.
Figure 1. Production configuration of a responsive image server with caching enabled on the frontend web server

We must also consider the security implications of allowing arbitrary requests to perform image resize operations. Having caching configured does not help if an attacker were to make rapid requests to unique image asset variants such as /img1001/mylogo.png, /img1002/mylogo.png, /img1003/mylogo.png and so on. Even with a relatively low volume of requests, such attacks may cause denial of service because of excessive CPU utilization. To address this, we apply a rate limit to the responsive image server, but not to the frontend server containing the cached variants. The following configuration extends the configuration in Matching Image Size to Pixel Density by applying caching and rate limiting to the Image‑Filter module.

proxy_cache_path /var/www/imgcache levels=1 keys_zone=resized:1m max_size=256m;
server {
    listen 80;
    root /var/www/public_html;
    location ~ ^/img([0-9]+)(?:/(.*))?$ {
        proxy_cache resized;
        proxy_cache_valid 180m;
limit_req_zone "1" zone=2persec:32k rate=2r/s;
server {
    listen 9001;
    deny all;
    limit_req zone=2persec burst=10;
    location ~ ^/img([0-9]+)(?:/(.*))?$ {
        alias /var/www/master_images/$2;
        image_filter_buffer 10M;
        image_filter resize $1 -;

We start by defining the location of our cached images with the proxy_cache_path directive. The keys_zone parameter defines a shared memory zone for the cache index (called resized) and allocates 1 MB, which is sufficient for about 8,000 resized images. The max_size parameter defines the point at which NGINX Plus starts to remove the least recently requested images from the cache to make room for new cached items.

The location directive for the frontend web server (listen80) uses the proxy_pass directive to send requests prefixed with /img to the internally hosted responsive image server ( The proxy_cache directive enables caching for this location by specifying the name of the cache (resized) to use to store responses from the responsive image server. The proxy_cache_valid directive ensures that resized images are kept in the cache for at least 180 minutes (unless the cache has exceeded max_size and they are among the least recently requested) and that any erroneous responses from the responsive image server are not cached.

For an in‑depth description of caching, see A Guide to Caching with NGINX and NGINX Plus.

Before defining the responsive image server itself, we specify a rate limit with the limit_req_zone directive. The directive does not itself enforce a rate limit – it defines a rate limit of two requests per second that is then applied to the responsive image server by including the limit_req directive in its server block (see the next paragraph). Typically a rate limit is keyed against an attribute of the request but in this case we specify the static key value "1" so that the limit applies to all requestors. We set the size of the shared memory zone to the smallest possible value, 3 KB, because our key has a fixed cardinality of one.

The server block for the responsive image server listens on port 9001. We include the allow and deny directives to specify that only localhost (the frontend web server) can connect to the responsive image server. We then apply the previously defined rate limit by including the limit_req directive; the burst parameter allows 10 concurrent requests before enforcing the rate limit. Once the rate limit is in effect, excess requests are delayed until they can be processed within the limit.

The location block is identical to the one in Matching Image Size to Pixel Density but is now exercised only when the requested image is not in the cache and while the rate limit has not been exceeded.

Image processing can be very computationally intensive, so we recommend running an NGINX Plus instance dedicated to it so that the worker processes are isolated from those of the frontend web server. This protects against extremely high workloads and DoS attacks by avoiding the situation where NGINX Plus cannot immediately accept new requests because all worker processes are busy with image‑resizing requests. A separate instance of NGINX Plus can be run on the same host by specifying a different configuration file on the command line:

$ sudo nginx -c /etc/nginx/resize-server.conf

Responsive Images in Action

The most effective way to see responsive images in action is to observe a browser making decisions about which srcset image variant to use as the size of the viewport changes. Here is the HTML source of a simple image gallery. Note that for demonstration purposes the size variants are slightly different for each image, creating many possible “breakpoints” at which the browser can choose a different variant.

<!DOCTYPE html>



            <title>Responsive Image Gallery</title>


            <h2>Responsive Image Gallery</h2>
                <img src="/img100/1-dominos.jpg" sizes="(min-width: 20em) 40vw, 100vw"
                    srcset="/img110/1-dominos.jpg 110w, /img210/1-dominos.jpg 210w,
                        /img310/1-dominos.jpg 310w, /img410/1-dominos.jpg 410w,
                            /img510/1-dominos.jpg 510w, /img610/1-dominos.jpg 610w">
                <img src="/img100/2-sign.jpg" sizes="(min-width: 20em) 40vw, 100vw"
                    srcset="/img120/2-sign.jpg 120w, /img220/2-sign.jpg 220w,
                        /img330/2-sign.jpg 330w, /img420/2-sign.jpg 420w,
                            /img520/2-sign.jpg 520w, /img620/2-sign.jpg 620w">
                <img src="/img100/3-thruppence.jpg" sizes="(min-width: 20em) 40vw, 100vw"
                    srcset="/img130/3-thruppence.jpg 130w, /img230/3-thruppence.jpg 230w,
                        /img330/3-thruppence.jpg 330w, /img440/3-thruppence.jpg 440w,
                            /img550/3-thruppence.jpg 550w, /img660/3-thruppence.jpg 660w">
                <img src="/img100/4-aces.jpg" sizes="(min-width: 20em) 40vw, 100vw"
                    srcset="/img140/4-aces.jpg 140w, /img240/4-aces.jpg 240w,
                        /img340/4-aces.jpg 340w, /img440/4-aces.jpg 440w,
                            /img540/4-aces.jpg 540w, /img640/4-aces.jpg 640w">

The screenshots below show the contents of this web page in a Chrome browser with the Inspector open to the Network tab. The Name column shows the path of each image variant requested from the server, which allows us to see the chosen size without having to check the logs on the web server.

With the narrow viewport in Figure 2, the browser has chosen images between 220 and 310 pixels wide (the numerical suffixes on the /img directory names in the Name column range between those values).

Image illustrating responsive web design: when the viewport is narrow, a web browser chooses smaller size variants of each image, as generated by a responsive image server with the NGINX Plus Image-Filter module installed
Figure 2. Responsive image gallery when viewport is narrow

When we widen the browser window in Figure 3, the browser chooses images between 440 and 540 pixels wide (the last four images listed). The value for these images in the Initiator column is Other.

Image illustrating responsive web design: when the viewport is wider, a web browser chooses larger size variants of each image, as generated by a responsive image server with the NGINX Plus Image-Filter module installed
Figure 3. Responsive image gallery when viewport is wider


With NGINX Plus and the Image‑Filter module, we can deliver the optimum image size for the current browser conditions. And we can do this without pre‑production image resizing, batch processing, or having to manage hundreds of image asset variants on disk. Just another way in which NGINX Plus helps you achieve flawless application delivery.

The Performance Zone is brought to you in partnership with AppDynamics.  See Gartner’s latest research on the application performance monitoring landscape and how APM suites are becoming more and more critical to the business.


Published at DZone with permission of Liam Crilly, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}