<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: user-agents</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/user-agents.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-08-04T22:49:25+00:00</updated><author><name>Simon Willison</name></author><entry><title>ChatGPT agent's user-agent</title><link href="https://simonwillison.net/2025/Aug/4/chatgpt-agents-user-agent/#atom-tag" rel="alternate"/><published>2025-08-04T22:49:25+00:00</published><updated>2025-08-04T22:49:25+00:00</updated><id>https://simonwillison.net/2025/Aug/4/chatgpt-agents-user-agent/#atom-tag</id><summary type="html">
    &lt;p&gt;I was exploring how ChatGPT agent works today. I learned some interesting things about how it exposes its identity through HTTP headers, then made a huge blunder in thinking it was leaking its URLs to Bingbot and Yandex... but it turned out &lt;a href="https://simonwillison.net/2025/Aug/4/chatgpt-agents-agent/#cloudflare-crawler-hints"&gt;that was a Cloudflare feature&lt;/a&gt; that had nothing to do with ChatGPT.&lt;/p&gt;

&lt;p&gt;ChatGPT agent is the &lt;a href="https://openai.com/index/introducing-chatgpt-agent/"&gt;recently released&lt;/a&gt; (and confusingly named) ChatGPT feature that provides browser automation combined with terminal access as a feature of ChatGPT - replacing their previous &lt;a href="https://help.openai.com/en/articles/10421097-operator"&gt;Operator research preview&lt;/a&gt; which is scheduled for deprecation on August 31st.&lt;/p&gt;

&lt;h4 id="investigating-chatgpt-agent-s-user-agent"&gt;Investigating ChatGPT agent's user-agent&lt;/h4&gt;
&lt;p&gt;I decided to dig into how it works by creating a logged web URL endpoint using &lt;a href="https://simonwillison.net/2024/Aug/8/django-http-debug/"&gt;django-http-debug&lt;/a&gt;. Then I told ChatGPT agent mode to explore that new page:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/chatgpt-agent-url.jpg" alt="ChatGPT screenshot. My prompt was &amp;quot;Visit https://simonwillison.net/test-url-context and tell me what you see there&amp;quot; - it said &amp;quot;Worked for 15 seconds&amp;quot; with an arrow, then a screnshot of the webpage content showing &amp;quot;simonwillison.net&amp;quot; with a favicon, heading &amp;quot;This is a heading&amp;quot;, text &amp;quot;Text and text and more text.&amp;quot; and &amp;quot;this came from javascript&amp;quot;. The bot then responds with: The webpage displays a simple layout with a large heading at the top that reads “This is a heading.” Below it, there's a short paragraph that says “Text and text and more text.” A final line appears underneath saying “this came from javascript,” indicating that this last line was inserted via a script. The page contains no interactive elements or instructions—just these lines of plain text displayed on a white background." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;My logging captured these request headers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Via: 1.1 heroku-router
Host: simonwillison.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Cf-Ray: 96a0f289adcb8e8e-SEA
Cookie: cf_clearance=zzV8W...
Server: Heroku
Cdn-Loop: cloudflare; loops=1
Priority: u=0, i
Sec-Ch-Ua: "Not)A;Brand";v="8", "Chromium";v="138"
Signature: sig1=:1AxfqHocTf693inKKMQ7NRoHoWAZ9d/vY4D/FO0+MqdFBy0HEH3ZIRv1c3hyiTrzCvquqDC8eYl1ojcPYOSpCQ==:
Cf-Visitor: {"scheme":"https"}
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Cf-Ipcountry: US
X-Request-Id: 45ef5be4-ead3-99d5-f018-13c4a55864d3
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Accept-Encoding: gzip, br
Accept-Language: en-US,en;q=0.9
Signature-Agent: "https://chatgpt.com"
Signature-Input: sig1=("@authority" "@method" "@path" "signature-agent");created=1754340838;keyid="otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg";expires=1754344438;nonce="_8jbGwfLcgt_vUeiZQdWvfyIeh9FmlthEXElL-O2Rq5zydBYWivw4R3sV9PV-zGwZ2OEGr3T2Pmeo2NzmboMeQ";tag="web-bot-auth";alg="ed25519"
X-Forwarded-For: 2a09:bac5:665f:1541::21e:154, 172.71.147.183
X-Request-Start: 1754340840059
Cf-Connecting-Ip: 2a09:bac5:665f:1541::21e:154
Sec-Ch-Ua-Mobile: ?0
X-Forwarded-Port: 80
X-Forwarded-Proto: http
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;strong&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36&lt;/strong&gt; user-agent header is the one used by the most recent Chrome on macOS - which is a little odd here as the &lt;strong&gt;Sec-Ch-Ua-Platform : "Linux"&lt;/strong&gt; indicates that the agent browser runs on Linux.&lt;/p&gt;
&lt;p&gt;At first glance it looks like ChatGPT is being dishonest here by not including its bot identity in the user-agent header. I thought for a moment it might be reflecting my own user-agent, but I'm using Firefox on macOS and it identified itself as Chrome.&lt;/p&gt;
&lt;p&gt;Then I spotted this header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Signature-Agent: "https://chatgpt.com"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is accompanied by a much more complex header called &lt;strong&gt;Signature-Input&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Signature-Input: sig1=("@authority" "@method" "@path" "signature-agent");created=1754340838;keyid="otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg";expires=1754344438;nonce="_8jbGwfLcgt_vUeiZQdWvfyIeh9FmlthEXElL-O2Rq5zydBYWivw4R3sV9PV-zGwZ2OEGr3T2Pmeo2NzmboMeQ";tag="web-bot-auth";alg="ed25519"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a &lt;code&gt;Signature&lt;/code&gt; header too.&lt;/p&gt;
&lt;p&gt;These turn out to come from a relatively new web standard: &lt;a href="https://www.rfc-editor.org/rfc/rfc9421.html"&gt;RFC 9421 HTTP Message Signatures&lt;/a&gt;' published February 2024.&lt;/p&gt;
&lt;p&gt;The purpose of HTTP Message Signatures is to allow clients to include signed data about their request in a way that cannot be tampered with by intermediaries. The signature uses a public key that's provided by the following well-known endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://chatgpt.com/.well-known/http-message-signatures-directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add it all together and we now have a rock-solid way to identify traffic from ChatGPT agent: look for the &lt;code&gt;Signature-Agent: "https://chatgpt.com"&lt;/code&gt; header and confirm its value by checking the signature in the &lt;code&gt;Signature-Input&lt;/code&gt; and &lt;code&gt;Signature&lt;/code&gt; headers.&lt;/p&gt;
&lt;h4 id="and-then-came-the-crawlers"&gt;And then came Bingbot and Yandex&lt;/h4&gt;
&lt;p&gt;Just over a minute after it captured that request, my logging endpoint got another request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Via: 1.1 heroku-router
From: bingbot(at)microsoft.com
Host: simonwillison.net
Accept: */*
Cf-Ray: 96a0f4671d1fc3c6-SEA
Server: Heroku
Cdn-Loop: cloudflare; loops=1
Cf-Visitor: {"scheme":"https"}
User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36
Cf-Ipcountry: US
X-Request-Id: 6214f5dc-a4ea-5390-1beb-f2d26eac5d01
Accept-Encoding: gzip, br
X-Forwarded-For: 207.46.13.9, 172.71.150.252
X-Request-Start: 1754340916429
Cf-Connecting-Ip: 207.46.13.9
X-Forwarded-Port: 80
X-Forwarded-Proto: http
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I pasted &lt;code&gt;207.46.13.9&lt;/code&gt; into Microsoft's &lt;a href="https://www.bing.com/toolbox/verify-bingbot-verdict"&gt;Verify Bingbot&lt;/a&gt; tool (after solving a particularly taxing CAPTCHA) and it confirmed that this was indeed a request from Bingbot.&lt;/p&gt;
&lt;p&gt;I set up a second URL to confirm... and this time got a visit from Yandex!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Via: 1.1 heroku-router
From: support@search.yandex.ru
Host: simonwillison.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cf-Ray: 96a16390d8f6f3a7-DME
Server: Heroku
Cdn-Loop: cloudflare; loops=1
Cf-Visitor: {"scheme":"https"}
User-Agent: Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)
Cf-Ipcountry: RU
X-Request-Id: 3cdcbdba-f629-0d29-b453-61644da43c6c
Accept-Encoding: gzip, br
X-Forwarded-For: 213.180.203.138, 172.71.184.65
X-Request-Start: 1754345469921
Cf-Connecting-Ip: 213.180.203.138
X-Forwarded-Port: 80
X-Forwarded-Proto: http
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yandex &lt;a href="https://yandex.com/support/webmaster/en/robot-workings/check-yandex-robots.html?lang=en"&gt;suggest a reverse DNS lookup&lt;/a&gt; to verify, so I ran this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dig -x 213.180.203.138 +short
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And got back:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;213-180-203-138.spider.yandex.com.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which confirms that this is indeed a Yandex crawler.&lt;/p&gt;

&lt;p&gt;I tried a third experiment to be sure... and got hits from both Bingbot and YandexBot.&lt;/p&gt;

&lt;h4 id="cloudflare-crawler-hints"&gt;It was Cloudflare Crawler Hints, not ChatGPT&lt;/h4&gt;

&lt;p&gt;So I wrote up and posted about my discovery... and &lt;a href="https://x.com/jatan_loya/status/1952506398270767499"&gt;Jatan Loya asked:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;do you have crawler hints enabled in cf?&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;And yeah, it turned out I did. I spotted this in my caching configuration page (and it looks like I must have turned it on myself at some point in the past):&lt;/p&gt;

&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/cloudflare-crawler-hints.jpg" alt="Screenshot of Cloudflare settings panel showing &amp;quot;Crawler Hints Beta&amp;quot; with description text explaining that Crawler Hints provide high quality data to search engines and other crawlers when sites using Cloudflare change their content. This allows crawlers to precisely time crawling, avoid wasteful crawls, and generally reduce resource consumption on origins and other Internet infrastructure. Below states &amp;quot;By enabling this service, you agree to share website information required for feature functionality and agree to the Supplemental Terms for Crawler Hints.&amp;quot; There is a toggle switch in the on position on the right side and a &amp;quot;Help&amp;quot; link in the bottom right corner." style="max-width: 100%" /&gt;&lt;/p&gt;

&lt;p&gt;Here's &lt;a href="https://developers.cloudflare.com/cache/advanced-configuration/crawler-hints/"&gt;the Cloudflare documentation for that feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I deleted my posts on Twitter and Bluesky (since you can't edit those and I didn't want the misinformation to continue to spread) and edited &lt;a href="https://fedi.simonwillison.net/@simon/114972968822349077"&gt;my post on Mastodon&lt;/a&gt;, then updated this entry with the real reason this had happened.&lt;/p&gt;

&lt;p&gt;I also changed the URL of this entry as it turned out Twitter and Bluesky were caching my social media preview for the previous one, which included the incorrect information in the title.&lt;/p&gt;

&lt;details&gt;&lt;summary&gt;Original "So what's going on here?" section from my post&lt;/summary&gt;

&lt;p&gt;&lt;em&gt;Here's a section of my original post with my theories about what was going on before learning about Cloudflare Crawler Hints.&lt;/em&gt;&lt;/p&gt;

&lt;h4 id="so-what-s-going-on-here-"&gt;So what's going on here?&lt;/h4&gt;
&lt;p&gt;There are quite a few different moving parts here.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I'm using Firefox on macOS with the 1Password and Readwise Highlighter extensions installed and active. Since I didn't visit the debug pages at all with my own browser I don't think any of these are relevant to these results.&lt;/li&gt;
&lt;li&gt;ChatGPT agent makes just a single request to my debug URL ...&lt;/li&gt;
&lt;li&gt;... which is proxied through both Cloudflare and Heroku.&lt;/li&gt;
&lt;li&gt;Within about a minute, I get hits from one or both of Bingbot and Yandex.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Presumably ChatGPT agent itself is running behind at least one proxy - I would expect OpenAI to keep a close eye on that traffic to ensure it doesn't get abused.&lt;/p&gt;
&lt;p&gt;I'm guessing that infrastructure is hosted by Microsoft Azure. The &lt;a href="https://openai.com/policies/sub-processor-list/"&gt;OpenAI Sub-processor List&lt;/a&gt; - though that lists Microsoft Corporation, CoreWeave Inc, Oracle Cloud Platform and Google Cloud Platform under the "Cloud infrastructure" section so it could be any of those.&lt;/p&gt;
&lt;p&gt;Since the page is served over HTTPS my guess is that any intermediary proxies should be unable to see the path component of the URL, making the mystery of how Bingbot and Yandex saw the URL even more intriguing.&lt;/p&gt;
&lt;/details&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/bing"&gt;bing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/privacy"&gt;privacy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/search-engines"&gt;search-engines&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cloudflare"&gt;cloudflare&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/chatgpt"&gt;chatgpt&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/browser-agents"&gt;browser-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/retractions"&gt;retractions&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="bing"/><category term="privacy"/><category term="search-engines"/><category term="user-agents"/><category term="ai"/><category term="cloudflare"/><category term="generative-ai"/><category term="chatgpt"/><category term="llms"/><category term="browser-agents"/><category term="retractions"/></entry><entry><title>Claude feature drop</title><link href="https://simonwillison.net/2025/May/2/claude-search/#atom-tag" rel="alternate"/><published>2025-05-02T18:45:57+00:00</published><updated>2025-05-02T18:45:57+00:00</updated><id>https://simonwillison.net/2025/May/2/claude-search/#atom-tag</id><summary type="html">
    &lt;p&gt;It's not in their &lt;a href="https://docs.anthropic.com/en/release-notes/claude-apps"&gt;release notes&lt;/a&gt; yet but Anthropic pushed some big new features today. &lt;a href="https://twitter.com/alexalbert__/status/1918349277962879218"&gt;Alex Albert&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We've improved web search and rolled it out worldwide to all paid plans. Web search now combines light Research functionality, allowing Claude to automatically adjust search depth based on your question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anthropic announced &lt;a href="https://www.anthropic.com/news/research"&gt;Claude Research&lt;/a&gt; a few weeks ago as a product that can combine web search with search against your private Google Workspace - I'm not clear on how much of that product we get in this "light Research" functionality.&lt;/p&gt;
&lt;p&gt;I'm most excited about &lt;a href="https://twitter.com/AnthropicAI/status/1918346819169374470"&gt;this detail&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can also drop a web link in any chat and Claude will fetch the content for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my experiments so far the user-agent it uses is &lt;code&gt;Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +Claude-User@anthropic.com)&lt;/code&gt;. It appears to obey &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/alex-albert"&gt;alex-albert&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm-tool-use"&gt;llm-tool-use&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deep-research"&gt;deep-research&lt;/a&gt;&lt;/p&gt;



</summary><category term="user-agents"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="anthropic"/><category term="claude"/><category term="alex-albert"/><category term="llm-tool-use"/><category term="deep-research"/></entry><entry><title>Running gunicorn behind nginx on Heroku for buffering and logging</title><link href="https://simonwillison.net/2017/Oct/2/nginx-heroku/#atom-tag" rel="alternate"/><published>2017-10-02T01:57:20+00:00</published><updated>2017-10-02T01:57:20+00:00</updated><id>https://simonwillison.net/2017/Oct/2/nginx-heroku/#atom-tag</id><summary type="html">
    &lt;p&gt;Heroku's default setup for Django uses the &lt;a href="http://gunicorn.org/"&gt;gunicorn&lt;/a&gt; application server. Each
Heroku dyno can only run a limited number of gunicorn workers, which means a
limited number of requests can be served in parallel (around 4 per dyno is a
good rule of thumb).&lt;/p&gt;

&lt;p&gt;Where things get nasty is when you have devices on slow connections - like
mobile phones. Heroku's router buffers headers but it does not buffer response
bodies, so a slow device could hold up a gunicorn worker for several seconds.
Too many slow devices at once and the site will become unavailable to other
users.&lt;/p&gt;

&lt;p&gt;This issue is explained and discussed here: &lt;a href="http://blog.etianen.com/blog/2014/01/19/gunicorn-heroku-django/"&gt;Don't use Gunicorn to host your Django sites on Heroku &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That article recommends using waitress as an alternative to gunicorn, but in
the comments at the bottom of the article people suggest using a Heroku
&lt;a href="https://github.com/beanieboi/nginx-buildpack"&gt;nginx-buildpack&lt;/a&gt; as an alternative.&lt;/p&gt;

&lt;p&gt;Here is a slightly out-of-date tutorial on getting this all set up: &lt;a href="https://koed00.github.io/Heroku_setups/"&gt;https://koed00.github.io/Heroku_setups/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used the following commands to set up the buildpacks:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;heroku stack:set cedar-14
heroku buildpacks:clear
heroku buildpacks:add https://github.com/beanieboi/nginx-buildpack.git
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-python.git
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unfortunately the nginx buildpack is not yet compatible with the new &lt;samp&gt;heroku-16&lt;/samp&gt;
stack, so until the nginx buildpack has been updated it's necessary to run the
application on the older &lt;samp&gt;cedar-14&lt;/samp&gt; stack. See this discussion for details: &lt;a href="https://github.com/ryandotsmith/nginx-buildpack/issues/68"&gt;ryandotsmith/nginx-buildpack#68&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding nginx in this way also gives us the opportunity to fix another
limitation of Heroku: its default logging configuration. By default, log lines produced by Heroku (visible using &lt;samp&gt;heroku logs --tail&lt;/samp&gt; or with a logging addon such as &lt;a href="https://elements.heroku.com/addons/papertrail"&gt;Papertrail&lt;/a&gt;) look like
this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    Oct 01 18:01:06 simonwillisonblog heroku/router: at=info
        method=GET path="/2017/Oct/1/ship/" host=simonwillison.net
        request_id=bb22f67e-6924-4e81-b6ad-74d1f465cda7
        fwd="2001:8003:74c5:8b00:79e4:80ed:fa85:7b37,108.162.249.198"
        dyno=web.1 connect=0ms service=338ms status=200 bytes=4523 protocol=http
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notably missing here is both the user-agent string and the referrer header
sent by the browser! If you're a fan of tailing log files these omissions are pretty
disappointing.&lt;/p&gt;

&lt;p&gt;The nginx buildback I'm using loads a default configuration file at
&lt;samp&gt;config/nginx.conf.erb&lt;/samp&gt;. By including &lt;a href="https://github.com/simonw/simonwillisonblog/blob/ad874a2bf9ebfeffcb0a1a7f8594ad9735fcfc01/config/nginx.conf.erb"&gt;my own copy of this file&lt;/a&gt; I can override
the original and define my own custom log format.&lt;/p&gt;

&lt;p&gt;Having applied this change, the new log lines look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    2017-10-02T01:44:38.762845+00:00 app[web.1]:
        measure#nginx.service=0.133 request="GET / HTTP/1.1" status_code=200
        request_id=8b6402de-d072-42c4-9854-0f71697b30e5 remote_addr="10.16.227.159"
        forwarded_for="199.188.193.220" forwarded_proto="http" via="1.1 vegur"
        body_bytes_sent=12666 referer="-" user_agent="Mozilla/5.0 (Macintosh;
        Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko)
        Chrome/61.0.3163.100 Safari/537.36"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;This blog entry started life as &lt;a href="https://github.com/simonw/simonwillisonblog/commit/23615a4822ab463c611a3e6a1f4d6cb4dcfc5e7b"&gt;a commit message&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/logging"&gt;logging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nginx"&gt;nginx&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/heroku"&gt;heroku&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gunicorn"&gt;gunicorn&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="django"/><category term="logging"/><category term="nginx"/><category term="user-agents"/><category term="heroku"/><category term="gunicorn"/></entry><entry><title>Changes in Opera's user agent string format</title><link href="https://simonwillison.net/2009/May/28/opera/#atom-tag" rel="alternate"/><published>2009-05-28T01:16:36+00:00</published><updated>2009-05-28T01:16:36+00:00</updated><id>https://simonwillison.net/2009/May/28/opera/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://dev.opera.com/articles/view/opera-ua-string-changes/"&gt;Changes in Opera&amp;#x27;s user agent string format&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
How depressing... Opera 10 will ship with 9.80 in the User-Agent string because badly written browser sniffing scripts can’t cope with double digits.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://blog.360.yahoo.com/blog-TBPekxc1dLNy5DOloPfzVvFIVOWMB0li?p=1008"&gt;Douglas Crockford&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/browsers"&gt;browsers&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/browsersniffing"&gt;browsersniffing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/opera"&gt;opera&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;



</summary><category term="browsers"/><category term="browsersniffing"/><category term="opera"/><category term="user-agents"/></entry><entry><title>minidetector</title><link href="https://simonwillison.net/2008/Aug/15/minidetector/#atom-tag" rel="alternate"/><published>2008-08-15T08:21:59+00:00</published><updated>2008-08-15T08:21:59+00:00</updated><id>https://simonwillison.net/2008/Aug/15/minidetector/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://code.google.com/p/minidetector/"&gt;minidetector&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat piece of Django middleware that adds a “mobile = True” attribute to the request object if the request’s user-agent matches a list of strings of known low-power browsers in mobiles, PDAs or game consoles.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/middleware"&gt;middleware&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/minidetector"&gt;minidetector&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mobile"&gt;mobile&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="middleware"/><category term="minidetector"/><category term="mobile"/><category term="python"/><category term="user-agents"/></entry><entry><title>Browser detection reconsidered</title><link href="https://simonwillison.net/2003/Feb/24/browserDetectionReconsidered/#atom-tag" rel="alternate"/><published>2003-02-24T12:07:53+00:00</published><updated>2003-02-24T12:07:53+00:00</updated><id>https://simonwillison.net/2003/Feb/24/browserDetectionReconsidered/#atom-tag</id><summary type="html">
    &lt;p&gt;Leonard Lin on &lt;a href="http://randomfoo.net/?p=2003_02_23_archive.inc#90365326"&gt;The Folly of Depending on CSS Parsing Bugs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote cite="http://randomfoo.net/?p=2003_02_23_archive.inc#90365326"&gt;
&lt;p&gt;I would not compensate for CSS rendering bugs by exploiting CSS parsing bugs except as a last resort. Think about it from a standardized test perspective: what strong relation does CSS rendering bugs have with CSS parsing bugs? There's no reason (nor right!) to assume that all future browsers with the same rendering bugs will have the same parsing bugs (and vice versa). In fact, if you look at the recent past releases (Safari, Opera, IE), even within browser families you'll see that this is absolutely not true!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He has an interesting point - user agent sniffing, while derided by many, is at least predictable in that you can deliberately target specific versions of specific browsers (as long as you're careful not to feed a user-agent cloaked Opera something nasty by mistake). &lt;acronym title="Cascading Style Sheets"&gt;CSS&lt;/acronym&gt; hacks may target browsers based purely on their capabilities (which cannot be cloaked by a false user agent string) but require careful maintenance against future version changes - as with Safari which has &lt;a href="http://www.mozillazine.org/weblogs/hyatt/archives/2003_02.html#002553" title="Safari Newsflash: CSS Parser Integrated"&gt;fixed some CSS parsing errors&lt;/a&gt; that were being used to filter Safari specific styles.&lt;/p&gt;

&lt;p&gt;I suppose it boils down to the question of which is easier to maintain - a site-wide stylesheet (or two) with hacks in vs a server side (I'll ignore client side as it's even uglier) browser detection routine to serve up the right stylesheet. Either way, if you don't have a full set of browsers and platforms to test on the best you can do is cross your fingers and pray nothing breaks.&lt;/p&gt;

&lt;p&gt;Of course, this issue is true of table layouts as well. Who knows, maybe in about 5 years time the browsers will have caught up with the current set of standards (and we'll all be stressing over how much of &lt;acronym title="Cascading Style Sheets"&gt;CSS&lt;/acronym&gt;3 we can use without the house of cards tumbling back down again).&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leonard-lin"&gt;leonard-lin&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="css"/><category term="leonard-lin"/><category term="user-agents"/></entry><entry><title>Mozilla versions</title><link href="https://simonwillison.net/2002/Jun/20/mozillaVersions/#atom-tag" rel="alternate"/><published>2002-06-20T15:50:05+00:00</published><updated>2002-06-20T15:50:05+00:00</updated><id>https://simonwillison.net/2002/Jun/20/mozillaVersions/#atom-tag</id><summary type="html">
    &lt;p&gt;Peter-Paul Koch on evolt: &lt;a href="http://www.evolt.org/article/One_browser_many_names/21/29442/index.html"&gt;One browser, many names&lt;/a&gt; - an invaluable guide to the many different version numbers and user-agent strings resulting from the &lt;a href="http://www.mozilla.org/"&gt;Mozilla&lt;/a&gt; project.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/mozilla"&gt;mozilla&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="mozilla"/><category term="user-agents"/></entry><entry><title>User Agent list</title><link href="https://simonwillison.net/2002/Jun/15/userAgentList/#atom-tag" rel="alternate"/><published>2002-06-15T14:25:27+00:00</published><updated>2002-06-15T14:25:27+00:00</updated><id>https://simonwillison.net/2002/Jun/15/userAgentList/#atom-tag</id><summary type="html">
    &lt;p&gt;rudy on &lt;a href="http://lists.evolt.org/"&gt;thelist&lt;/a&gt; pointed me to this handy &lt;a href="http://www.psychedelix.com/agents.html"&gt;List of User Agents&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/user-agents"&gt;user-agents&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="user-agents"/></entry></feed>