> ## Content Index
> Fetch the complete content index at: https://www.ctrl.blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Make changes to static content with response body substitutions
- URL: https://www.ctrl.blog/entry/mod_substitute/
- Published: 2019-09-02T13:08:00.000Z
- Updated: 2026-08-23T22:14:37.000Z
- Description: Tweak the HTTP response body with regex substitutions. Apache HTTPD configuration example shows changing the URL tracking parameters in a syndication feed file.
- Author: Daniel Aleksandersen
- Tags: Apache HTTPD, Syndication feeds

Ctrl blog’s [syndication news feed](https://feed.ctrl.blog/latest.atom?ref=ctrl.blog) is called upon to handle a lot of different tasks and service integrations. It’s used to share new articles on Twitter, create the weekly newsletter, as well as handling integrations with syndication services like Apple News and Flipboard. It’s also used in a myriad of different feed readers by individual readers.

For some of these integrations, I require some minor customizations to the feed to fit with specific technical requirements if each platform. A top issue was the many conflicting requirements for what image sizes to use and how to embed them in the feed.

I’ve also added tracking parameters to the feed to differentiate between readers coming to the site from some primary external sources like the email newsletter, Twitter account, feed readers, and so on. This is the same task my old WordPress Feed to Google Analytics [integration plugin](https://www.ctrl.blog/entry/wordpress-feed-utm-plugin/) handled. (I don’t track individual subscribers or clients, only what channel is used to access the site.)

It was no problems handling this task with a dynamic site generator like WordPress. It could give slightly different responses to different clients based on their User-Agent or a URL query parameter. However, this poses a question when [I migrated off WordPress](https://www.ctrl.blog/entry/enough-of-wordpress/) and began using a static website generator.

The question became: how can I introduce some limited dynamic responses when all I had was statically generated files?

The answer: Dynamic responses from static content with request-specific substitutions performed by the web server on the edge. In other words, Apache HTTPD’s `mod_substitute` module.

The below Apache HTTPD configuration example shows how the string “#src=feed” is replaced inside the response body when the URL query string is either ?src=none, ?src=email, or ?src=apple-news. These changes are only applied for locations that end with .atom with the media-type application/atom+xml.

```apacheconf
<LocationMatch "\.atom$">
  <If "%{QUERY_STRING} =~ /src=none/">
    AddOutputFilterByType SUBSTITUTE application/atom+xml
    Substitute "s/#src=feed//nq"
  </If>
  <ElseIf "%{QUERY_STRING} =~ /src=email/">
    AddOutputFilterByType SUBSTITUTE application/atom+xml
    Substitute "s/#src=feed/#src=email/nq"
  </ElseIf>
  <ElseIf "%{QUERY_STRING} =~ /src=apple-news/">
    AddOutputFilterByType SUBSTITUTE application/atom+xml
    Substitute "s/#src=feed/#src=apple-news/nq"
  </ElseIf>
</LocationMatch>

```

You may also need to enable the `mod_substitute` module in your configuration.

The above configuration example is repetitive, but this is necessary because the substitute module can’t access environmental variables. This limitation will be removed in the upcoming Apache HTTPD 2.5.1 release.

You may need to change HTTPD’s filter chain if you use custom filters, or your web server is acting as a reverse proxy server. The most likely problem you’ll run into as a proxy server is that you’ll need to pass the response body through the deflate filter before applying substitute, and then reapply the compression filter.

You can achieve the same thing in Nginx with the `sub_filter` (exact string matches) or `subs_filter` (regular expressions) filters. Neither of these is built and included with Nginx by default, however.

#### Sources

- [Apache Module mod\_substitute](https://httpd.apache.org/docs/trunk/mod/mod%5Fsubstitute.html?ref=ctrl.blog), version 2.5.1, 2017-12-31, HTTPD Documentation, Apache
- [Apache Module mod\_substitute](https://httpd.apache.org/docs/2.4/mod/mod%5Fsubstitute.html?ref=ctrl.blog), version 2.4.17, 2015-10-12, HTTPD Documentation, Apache
- [Substitutions](https://www.nginx.com/resources/wiki/modules/substitutions/?ref=ctrl.blog), 2015-09-15, Nginx wiki, Nginx
- [Module ngx\_http\_sub\_module](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fsub%5Fmodule.html?ref=ctrl.blog), version 1.9.4, 2015-08-18, Nginx documentation, Nginx