Error 0x00000309 is ERROR_VERSION_PARSE_ERROR. It comes from HTTP.sys, the kernel driver behind IIS, WinRM, and anything else on Windows that answers on port 80 or 443. The driver read a version string out of an incoming request and couldn't make sense of it. In IIS logs you'll see this as HTTP substatus 809, usually with a 400 response. The client gets a blank page or a connection reset, and nothing useful gets written to the app log because the request never made it as far as your code.
The number looks scary. It isn't. In almost every case I've seen over the past decade, the string that broke it was a User-Agent header, followed by Accept and Expect. Something on the network is sending garbage in a version field. Your job is to figure out what and stop it.
Cause 1: A broken User-Agent header (fix this first)
The User-Agent header is supposed to look like Mozilla/5.0 (Windows NT 10.0; Win64; x64) .... Some clients mangle it. Old load balancers, misconfigured mobile apps, and security scanners are the usual suspects. I've watched a Palo Alto vulnerability scan generate thousands of these by appending a random string to the UA on every request.
Here's how to confirm it, in order:
- Open an elevated Command Prompt on the server hosting the site.
- Run
netsh trace start capture=yes tracefile=C:\temp\http.etlto begin capturing traffic (trace stops on reboot, so keep it short). - Let it run for a few minutes while the errors happen, then stop it:
netsh trace stop. - Open
C:\temp\http.etlin Microsoft Network Monitor or convert it to pcap withetl2pcapngand open in Wireshark. - Filter on
http.user_agentand look for anything that isn't a normal browser or bot string. Long runs of random characters, missing spaces, embedded control bytes — that's your culprit.
Once you've got the source IP, tell whoever owns that machine to fix their client. If you can't, the permanent fix is a URL Rewrite rule that rejects bad UAs before they reach the app:
<rule name="Block malformed User-Agent" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_USER_AGENT}" pattern="^[a-zA-Z]{1,3}/[0-9]\." negate="true" />
</conditions>
<action type="CustomResponse" statusCode="400" statusReason="Bad User-Agent" />
</rule>
Adjust the pattern to match whatever's actually coming in. After you apply it, refresh the site from a normal browser — you should get the page as usual. Hit it from the offending IP and you'll get a clean 400 instead of a blank response and a log entry full of 0x00000309.
Cause 2: Web application firewall or proxy rewriting headers
The second most common cause is a device sitting in front of the server. Barracuda, F5, some Citrix NetScaler builds, and a handful of older Squid versions have shipped with bugs where they rewrite the Accept or Expect header and drop the version token, or they inject a duplicate version. HTTP.sys sees Accept: / or Expect: 100-continue; version= and gives up.
To find this one, temporarily bypass the proxy. On the server itself, run:
curl -v -H "Host: yoursite.com" http://127.0.0.1/
If that works and requests through the proxy fail with 0x00000309, the proxy is your problem. Check its rules on header rewriting. On F5, look at the HTTP profile's "Insert X-Forwarded-For" and "Accept-Encoding" settings; on Barracuda, disable "Header normalization" for the affected service and see if the errors stop. If they do, upgrade the firmware — most of these vendors patched the bug years ago and the box is just out of date.
Real-world example: a customer had an F5 running v11.5 (EOL since 2018) that was corrupting the Expect header for POSTs larger than 8 KB. Every file upload triggered 0x00000309. Firmware upgrade to v16 fixed it. No app change needed.
Cause 3: Custom apps sending a bad version token
If the bad requests originate from your own code — a desktop client, a mobile app, a microservice — you're sending a header that doesn't match RFC 7230. The grammar for HTTP-version is HTTP-name "/" DIGIT "." DIGIT, so "HTTP/1.1" is valid, "HTTP/1" is not, "HTTP 1.1" is not, and "HTTP/1.1.0" is not. HTTP.sys is strict about this. It's also strict about the version string inside headers like Expect, where 100-continue is the only defined value.
Grep your codebase for header construction. In .NET:
// Wrong - will trigger 0x00000309
request.Headers.Add("User-Agent", "MyApp/" + version);
// Right - version number, no extra dots, no spaces
request.Headers.Add("User-Agent", "MyApp/1.2.3");
Also check any middleware that sets headers dynamically. I've seen a logging library insert a build number with three dots and break every request quietly for a week before anyone noticed. Run netsh http show servicestate to confirm the requests are actually hitting HTTP.sys, then use the trace method from Cause 1 to capture the exact bytes on the wire.
What doesn't fix 0x00000309
- Restarting IIS. The error is per-request, not per-service. A restart clears the symptom for a few seconds, nothing more.
- Reinstalling .NET. This has nothing to do with .NET. It's the kernel driver.
- Tweaking the
UrlSegmentMaxLengthregistry key. That's for path parsing, not version strings. Wrong tool. - Disabling HTTP keep-alives. Occasionally masks the symptom if a proxy is pipelining badly, but it doesn't address the cause.
Quick reference
| Cause | How to confirm | Fix |
|---|---|---|
| Malformed User-Agent from a scanner or old client | netsh trace, open in Wireshark, check http.user_agent | Block bad UAs with URL Rewrite, or fix the client |
| Proxy or WAF rewriting headers | curl to 127.0.0.1 works, proxy fails | Update firmware, disable header normalization |
| Custom app sending bad version token | Grep source for header construction, check version strings | Match RFC 7230 grammar: HTTP/DIGIT.DIGIT |
Nine times out of ten it's the User-Agent. Fix that first, confirm the fix by watching the IIS log for HTTP substatus 809 to stop appearing, then move on to the other two if it doesn't.