IIS

A URL Rewrite rule with ARR in your IIS site's web.config.

Markdown

On IIS, a URL Rewrite rule recognizes bots and, with Application Request Routing (ARR) as a proxy, serves them Jazui's page. If your application is ASP.NET Core, it's simpler and more robust to use the ASP.NET Core package.

Before you start

  • The URL Rewrite and Application Request Routing modules installed (from Microsoft's IIS website).
  • Your site verified before enabling the rule: with ARR, if Jazui answers with an error, the bot gets that error.
  • Your token.

Installation

Open a console as administrator and run these two commands. The first enables ARR's proxy; the second lets the rule write the headers Jazui needs:

cmd
%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/proxy /enabled:"True" /commit:apphost

%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/rewrite/allowedServerVariables /+"[name='HTTP_AUTHORIZATION']" /+"[name='HTTP_X_JAZUI_URL']" /+"[name='HTTP_X_JAZUI_CLIENT_IP']" /+"[name='HTTP_COOKIE']" /commit:apphost

Then put this web.config in your site's folder (or copy its two rules into your existing one, Jazui's before the SPA's):

web.configDownload
<?xml version="1.0" encoding="utf-8"?>
<!--
  Jazui para IIS (SPA estática): los bots reciben el HTML renderizado; el resto, la SPA.
  Requiere URL Rewrite y Application Request Routing con el proxy activado, y permitir las variables del servidor
  (ver README.md). Sustituye [JAZUI_TOKEN] por el token de render del sitio.
  Las listas de bots y extensiones las rellena Sync-Integrations.ps1 (bots.txt, extensions.txt).
-->
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Jazui" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <!-- Un bot de la lista o _escaped_fragment_ en la query (el user-agent y la query separados por #) -->
            <!-- BOTS:BEGIN -->
            <add input="{HTTP_USER_AGENT}#{QUERY_STRING}" pattern="^[^#]*(?:googlebot|google-inspectiontool|storebot-google|adsbot-google|bingbot|bingpreview|msnbot|adidxbot|yandex|baiduspider|duckduckbot|slurp|applebot|seznambot|qwantbot|qwantify|petalbot|sogou|naver|exabot|yetibot|360spider|soso|gptbot|chatgpt|oai-searchbot|claudebot|claude-user|claude-searchbot|anthropic-ai|perplexity|google-cloudvertexbot|bytespider|ccbot|amazonbot|meta-externalagent|cohere-ai|mistralai-user|youbot|duckassistbot|yibot|qwenbot|tongyibot|erniebot|yiyanbot|doubaobot|tiktokspider|deepseekbot|pangubot|manus-user|kimi-agent|kimi-searchbot|kimi-user|kimibot|moonshotbot|hunyuan|chatglm-spider|agi agent|ai2bot|ai2bot-deepresearcheval|ai2bot-dolma|aihitbot|amazon-kendra|amazon-qbusiness|amazonbuyforme|amzn-searchbot|amzn-user|bedrockbot|novaact|andibot|claude-web|apifybot|apifywebsitecontentcrawler|atlassian-bot|awario|bigsur\.ai|brightbot|buddybot|cartai agentic commerce|terracotta|cledarabot|cloudflare-autorag|cloudflarebrowserrenderingcrawler|cohere-training-data-crawler|cragcrawler|crawlspace|datenbank crawler|diffbot|diffbot-user|anomura|echobot bot|echoboxbot|elevenlabsbot|elevenlabscrawler|exasearchbot|exauser|factset_spyderbot|firecrawlagent|geisthaus-pagefetcher|cloudvertexbot|gemini-deep-research|google-agent|google-firebase|google-notebooklm|googleagent-mariner|googleagent-urlcontext|googleother|googleother-image|googleother-video|notebooklm|iaskbot|iaskspider|imagesiftbot|img2dataset|isscyberriskcrawler|kagi-fetcher|keenablebot|klaviyoaibot|laiondownloader|linguee bot|linkupbot|aiwebindex|meta-externalfetcher|meta-webindexer|facebookbot|azureai-searchbot|mistralai-index|mistralai-training|mozilla-tabstack|nagetbot|netestate imprint crawler|icc-crawler|oai-adsbot|panscient|panscient\.com|shapbot|payhawk invoice fetching agent|phindbot|poggio-citations|poseidon research crawler|qualifiedbot|wpbot|querit-searchbot|queritbot|quillbot|quillbot\.com|reflectionbot|cotoyogi|sbintuitionsbot|scrybot|sidetrade indexer bot|tavilybot|agenttimes|thinkbot|timpibot|twinagent|velenpublicwebcrawler|wardbot|omgili|omgilibot|grok bot|grokappandroid|grokbot|addsearchbot|agentdatabot|aranet-searchbot|bixelbot|bravebot|channel3bot|friendlycrawler|henkbot|imagespider|kangaroo bot|kunatocrawler|laion-huggingface-processor|linerbot|mycentralaiscraperbot|terra cotta|wrtnbot|zanistabot|facebookexternalhit|facebot|twitterbot|linkedinbot|slackbot|whatsapp|telegrambot|discordbot|pinterest|redditbot|skypeuripreview|embedly|quora link preview|showyoubot|outbrain|developers\.google\.com\/\+\/web\/snippet|vkshare|tumblr|bitlybot|nuzzel|flipboard|snapchat|viber|mattermost|bitrix link preview|xing-contenttabreceiver|ahrefsbot|semrushbot|mj12bot|majestic-12|dotbot|rogerbot|screaming frog|sitebulb|serpstatbot|seokicks|siteimprove|lighthouse|google page speed|gtmetrix|pingdom|uptimerobot|uptimebot|w3c_validator|w3c-checklink|archive\.org_bot|paperlibot|dataprovider\.com|grapeshotcrawler|zoominfobot|feedly|newsblur|inoreader|integration-test)|#.*_escaped_fragment_" />
            <!-- BOTS:END -->
            <add input="{REQUEST_METHOD}" pattern="^(GET|HEAD)$" />
            <add input="{HTTP_X_JAZUI}" pattern=".+" negate="true" />
            <!-- EXTENSIONS:BEGIN -->
            <add input="{URL}" pattern="\.(js|mjs|css|less|map|json|xml|rss|txt|webmanifest|ico|png|jpg|jpeg|gif|webp|avif|svg|bmp|tif|tiff|psd|ai|woff|woff2|ttf|otf|eot|mp3|m4a|wav|ogg|mp4|m4v|webm|avi|mov|mpg|mpeg|wmv|flv|swf|pdf|doc|docx|xls|xlsx|ppt|pptx|csv|zip|gz|rar|7z|iso|torrent|exe|dmg|apk|wasm|dll|dat|blat)$" negate="true" />
            <!-- EXTENSIONS:END -->
          </conditions>
          <serverVariables>
            <set name="HTTP_AUTHORIZATION" value="Bearer [JAZUI_TOKEN]" />
            <!-- Si el sitio se sirve por http, cambia el esquema -->
            <set name="HTTP_X_JAZUI_URL" value="https://{HTTP_HOST}{UNENCODED_URL}" />
            <set name="HTTP_X_JAZUI_CLIENT_IP" value="{REMOTE_ADDR}" />
            <!-- Las cookies del visitante no le hacen falta a Jazui: no se envían -->
            <set name="HTTP_COOKIE" value="" />
          </serverVariables>
          <action type="Rewrite" url="https://api.jazui.com/Render" appendQueryString="false" />
        </rule>
        <!-- Fallback de la SPA: todo lo que no es un archivo va a index.html -->
        <rule name="SPA" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/index.html" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Replace [JAZUI_TOKEN] with your token. The address sent to Jazui starts with https://: if your site runs on http, change it in HTTP_X_JAZUI_URL.

What it does

  • The Jazui rule checks the user agent and _escaped_fragment_ in a single pattern, the method, the X-Jazui header and that the path isn't a file.
  • It writes Authorization, X-Jazui-Url and X-Jazui-Client-IP as server variables and rewrites the request to https://api.jazui.com/Render, which ARR resolves as a proxy.
  • The SPA rule sends everything that isn't a file or folder to index.html.

Keeping the bot list up to date

This configuration has the bot list written inside it, between the BOTS:BEGIN and BOTS:END markers, because the server can't download it on its own. When Jazui adds new bots:

  1. In the dashboard, go to Websites and click Site token on any of your sites.
  2. Under Bots in nginx, IIS and Apache, choose your server's tab and copy the block. You'll see how many bots it has and the list date.
  3. Replace everything between BOTS:BEGIN and BOTS:END in your configuration with that block and reload the server.

You can also download the list as text with GET https://api.jazui.com/v2/bots.txt or the ready-made blocks with GET https://api.jazui.com/v2/bots/config.

<p>Request your home page as if you were a bot, with the test user agent <code>integration-test</code>:</p><pre><code>curl -I -A "integration-test" https://mysite.com/</code></pre><p>The response must include <code>x-jazui: true</code>, with <code>x-jazui-cache: MISS</code> the first time and <code>HIT</code> afterwards. Without a bot user agent (<code>curl -I https://mysite.com/</code>) you should get your usual website, without <code>x-jazui</code>. In the dashboard, the request shows up in <b>Bot traffic</b> and the page in <b>Cached pages</b>.</p>

Request your home page as if you were a bot, with the test user agent integration-test:

curl -I -A "integration-test" https://mysite.com/

The response must include x-jazui: true, with x-jazui-cache: MISS the first time and HIT afterwards. Without a bot user agent (curl -I https://mysite.com/) you should get your usual website, without x-jazui. In the dashboard, the request shows up in Bot traffic and the page in Cached pages.

If something doesn't work

  • Error 500.50 or «server variable not allowed»: the second command (allowed variables) is missing.
  • 404 or 502.3 when requesting as a bot: ARR's proxy isn't enabled (first command) or the server can't reach the Internet on port 443.
  • Behind Cloudflare, {REMOTE_ADDR} is the CDN's IP: use {HTTP_CF_CONNECTING_IP} (or {HTTP_X_FORWARDED_FOR}) in HTTP_X_JAZUI_CLIENT_IP.
  • The maximum wait is ARR's proxy timeout (server settings, Application Request Routing Cache › Server Proxy Settings › Time-out); 20 seconds is a good value.