Skip to main content

IIS image optimization in 2026: WebP + AVIF without nginx

Updated 2026-05-20 · We-Amp B.V. · about an 8-minute read

Almost every WebP-and-AVIF tutorial on the web assumes nginx and a Linux box with a build step. On Microsoft IIS the same problem looks different. There is no .htaccess, the worker process runs as a constrained Windows identity, and the obvious map $http_accept pattern from nginx land has no direct equivalent. This page is what we tell IIS shops that ask: how do I actually ship WebP and AVIF on Windows Server, in 2026, without standing up nginx in front?

Why IIS makes this awkward

Three constraints turn a one-page nginx recipe into a multi-step question on IIS:

  • No per-directory configuration override. IIS reads a single applicationHost.config plus per-application web.config files. There is no .htaccess equivalent that the application owner can drop into the document root, so the typical "convert and serve WebP per directory" recipe becomes a request to the server administrator.
  • The static-file handler does not negotiate on Accept. The IIS static handler matches by URL, not by the Accept header. Shipping hero.webp next to hero.jpg means the application has to emit a <picture> element with source elements. There is no built-in server-side negotiation.
  • Application-pool identity gotchas. If you generate variants at build time and write them into the document root, the IIS application pool identity (commonly IIS APPPOOL\<site> or, on older installs, IUSR) needs read access. A pre-build job running as your developer account often produces files the worker process cannot serve until ACLs are sorted out: a class of bug that wastes an afternoon every time.

The combination is why the typical IIS site ships JPEGs and PNGs at source resolution and full quality, loses Largest Contentful Paint by seconds, and never gets the WebP-and-AVIF treatment that comparable nginx sites picked up years ago. There are two practical ways out.

Path 1: classic IIS with mod_pagespeed 1.1

mod_pagespeed 1.1 ships a native IIS module that handles content negotiation transparently. It is the same code as the IISpeed module, now part of the 1.1 multi-port release. It plugs into the IIS request pipeline and rewrites responses before they leave the worker process. The configuration block lives in applicationHost.config:

<ModPagespeed enabled="true">
  <Settings>
    <add name="ModPagespeedRewriteLevel" value="CoreFilters" />
    <add name="ModPagespeedEnableFilters"
         value="recompress_images,convert_jpeg_to_webp,resize_images,insert_image_dimensions,lazyload_images" />
    <add name="ModPagespeedFileCachePath"
         value="C:\ProgramData\mod_pagespeed\cache" />
    <add name="ModPagespeedFileCacheSizeKb" value="1048576" />
  </Settings>
</ModPagespeed>

Four filters carry the image work:

  • recompress_images: re-encodes JPEG and PNG sources at a sane quality target. The module decides the encoder and quality per image, not per site.
  • convert_jpeg_to_webp: generates a WebP variant of each JPEG and serves it to browsers that send Accept: image/webp. The original JPEG is still available for browsers that do not.
  • resize_images: when the rendered dimensions are smaller than the source (a hero image declared at 1200×630 in the HTML but sourced as a 4000×3000 camera JPEG), the module resizes the actual bytes to match.
  • insert_image_dimensions: writes width and height attributes onto <img> tags that lack them, removing one of the common sources of Cumulative Layout Shift.

AVIF is a separate consideration. The 1.1 module produces AVIF variants when its worker is built with AVIF support enabled; the decision lives at the worker layer, not at a named filter. Sites upgrading from IISpeed can confirm rewrites are running by hitting /pagespeed_global_admin and looking at the image rewrite counters (image_rewrites, image_webp_rewrites).

On a typical SharePoint or DotNetNuke front page, the visible effect is a hero image that goes from ~600 kB down to under 80 kB on modern browsers, a Largest Contentful Paint improvement of seconds on latency-bound mobile clients.

Path 2: ASP.NET Core with WeAmp.PageSpeed.AspNetCore

If your application is ASP.NET Core (.NET 8 or .NET 9), whether you host it behind IIS via the ASP.NET Core Module or you run Kestrel directly, the IIS-module path is the wrong layer to optimize at. The request never gets to the IIS handler that mod_pagespeed 1.1 plugs into; it flows through Kestrel, the ASP.NET Core pipeline, and back out. The right place to do image work is inside the pipeline, as middleware.

The WeAmp.PageSpeed.AspNetCore NuGet package wraps the same PageSpeed Automatic optimization library used by mod_pagespeed 1.1, repackaged as middleware. Two lines in Program.cs:

using WeAmp.PageSpeed.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddPageSpeed(options =>
{
    options.RewriteLevel = "CoreFilters";
    options.EnableFilters = new[]
    {
        "recompress_images",
        "convert_jpeg_to_webp",
        "resize_images",
        "insert_image_dimensions",
        "lazyload_images",
    };
    options.FileCachePath = "/var/cache/pagespeed";
});

var app = builder.Build();

app.UsePageSpeed();   // before app.UseStaticFiles() if you want to optimize static assets
app.UseStaticFiles();
app.MapControllers();
app.Run();

The middleware reads the request's Accept header, decides whether to serve WebP or AVIF, and emits the appropriate Vary: Accept response header so downstream caches do not cross-contaminate variants. The file cache directory should be writable by the application user. On classic IIS hosting that is the application pool identity; on a Docker host it is whatever user the container runs as.

Verifying the install

Whichever path you take, the verification recipe is the same. From a client off the server itself, request the hero image with an explicit Accept header and inspect the response:

# Should return image/webp on a modern browser request
curl -sI -H "Accept: image/webp,image/avif,image/*,*/*" \
  https://your-site/hero.jpg

# Should return image/jpeg for clients that do not advertise WebP
curl -sI -H "Accept: image/*,*/*" \
  https://your-site/hero.jpg

On a successful install, the two requests return different Content-Type values for the same URL, which means the server is negotiating. Both responses carry an X-Mod-Pagespeed header (1.1 module) or an X-PageSpeed header (ASP.NET Core middleware), so you can confirm the optimization layer actually saw the request. A Vary: Accept response header is also present, which is the signal CDNs need in order to cache the variants separately.

When images are not the bottleneck

A few caveats worth surfacing, because image optimization is the easy headline:

  • If your Largest Contentful Paint element is a chunk of text rendered by an above-the-fold JavaScript bundle, no amount of image-format negotiation moves the metric. Run a measurement first; the IIS Core Web Vitals guide covers the workflow.
  • If you are already on a CDN with on-the-fly image optimization (Cloudinary, imgproxy, Cloudflare Image Resizing), running another optimizer underneath usually overlaps without benefit. The two paths on this page are for shops doing the image work themselves, either because the CDN cost is wrong at their scale, or because the images are behind authentication and a public CDN is not an option.
  • AVIF encoding is meaningfully more CPU-expensive than WebP. On high-traffic sites the worker's optimized-variant cache is what makes AVIF practical: you encode once per source asset, then serve from cache. If your origin runs on a constrained Windows Server VM, watch the image rewrite counters at /pagespeed_global_admin for a few days and confirm the encoder is not running on every request.

Where to start

New to PageSpeed optimization on IIS: install the module and run it unlicensed. It fully optimizes out of the box and simply adds an X-PageSpeed-Warn: unlicensed header, so you can measure both paths against your own pages before you buy. When you are ready for production, buy a license — a commercial license is required for production use, billed immediately on purchase via FastSpring. The mod_pagespeed 1.1 getting-started guide covers the classic-IIS install; the ASP.NET Core image-optimization walkthrough on modpagespeed.com goes deeper on the middleware path with a worked example. Existing IISpeed customers get the 1.1 IIS module via the free IISpeed license transfer.

Ship WebP and AVIF on IIS

Install the module and run it unlicensed to measure both the IIS module and the ASP.NET Core middleware against your own hero images — it fully optimizes and just adds an X-PageSpeed-Warn: unlicensed header. A commercial license is required for production use.

Related