Skip to main content
Blog|
How-to guides

HTTP 406 Not Acceptable: what it means and how to fix it

|
Apr 10, 2026|9 min read
HOW-TO GUIDESHTTP 406 Not Acceptable: whatit means and how to fix itHOSTNEYhostney.comApril 10, 2026

A 406 Not Acceptable means the server cannot produce a response that matches the format the client requested. The client sent an Accept header saying “I want the response in this format,” and the server said “I cannot serve it in that format.” The request itself is fine – the URL exists, authentication passed, the server is healthy – but the two sides cannot agree on a response format.

This is one of the less common HTTP errors, and when it does appear it is often caused by a misconfigured web application firewall, a browser extension modifying request headers, or an API client sending the wrong Accept header rather than a genuine content negotiation failure.

Quick reference#

SymptomLikely causeFix
406 in a browser on a specific siteModSecurity or WAF blocking the request based on Accept headerCheck WAF audit log for the triggering rule
406 on API callsWrong Accept header (requesting a format the API does not support)Check the API docs for supported formats and fix the Accept header
406 after installing a WordPress pluginPlugin modifying response headers or conflicting with content negotiationDeactivate plugins one by one to find the culprit
406 on all requests from one browserBrowser extension injecting or modifying Accept headersDisable extensions, test in incognito
406 from Hulu or streaming serviceDRM/geo-restriction returning 406 instead of 403Not a server config issue – the service is blocking your client or region
406 intermittentCaching layer serving a cached response with wrong Content-Type Purge the cache or add Vary: Accept to cache configuration

What content negotiation is#

HTTP has a built-in mechanism for clients and servers to agree on the format of a response. The client sends Accept headers describing what it can handle, and the server picks the best match from what it can produce.

The key headers involved:

Accept: text/html, application/json, */*
Accept-Language: en-US, en;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Charset: utf-8
  • Accept – the media types the client can process (HTML, JSON, XML, images, etc.)
  • Accept-Language – preferred languages
  • Accept-Encoding – compression formats the client supports
  • Accept-Charset – character encodings the client supports

The q values are quality weights from 0 to 1. en;q=0.9 means “I prefer English but it is not my first choice.” Omitting q defaults to 1.0 (highest priority).

When a server receives these headers, it compares them against the formats it can produce. If there is a match, it serves the response in that format and includes a Content-Type header telling the client what it got. If there is no match and the server is strict about content negotiation, it returns 406.

In practice, most web servers and frameworks are lenient. A browser sends Accept: text/html and the server returns HTML. An API client sends Accept: application/json and the server returns JSON. The 406 error only appears when something forces the server into strict negotiation mode or when the Accept header is genuinely incompatible.

What the HTTP specification says#

HTTP 406 is defined in RFC 9110, Section 15.5.7. The specification says the server cannot produce a representation for the target resource that would be acceptable to the client, based on the proactive content negotiation header fields in the request.

The server should generate a response body containing a list of available representations and their characteristics, so the client can choose. In practice, most servers return a generic error page or a JSON error body.

HTTP/1.1 406 Not Acceptable
Content-Type: application/json

{
  "error": "Not Acceptable",
  "message": "Supported formats: application/json, application/xml",
  "status": 406
}

The specification notes that a server may return the response in a default format rather than returning 406, even if the client did not explicitly request that format. This is why 406 errors are rare in normal browsing – most servers just serve HTML regardless of the Accept header.

Common causes#

ModSecurity or WAF blocking based on Accept header

The most common cause of 406 errors on production websites. ModSecurity and other web application firewalls inspect every request header, including Accept . Certain rules flag unusual or missing Accept headers as suspicious:

  • A request with no Accept header at all
  • An Accept header with unusual media types
  • An Accept header that matches a known attack pattern
  • A request where the Accept header does not match the URL pattern (requesting JSON from an HTML endpoint)

ModSecurity often returns 406 instead of 403 Forbidden for these blocks because the WAF is technically rejecting the request based on content negotiation headers. Check the ModSecurity audit log:

tail -50 /var/log/modsec_audit.log

Look for the rule ID that triggered the block. If it is a false positive, disable the specific rule:

SecRuleRemoveById 920420

Rule 920420 in the OWASP Core Rule Set is a common trigger – it flags requests with a restricted Accept header.

Wrong Accept header in API calls

API clients that send the wrong Accept header get 406 from strict APIs:

# This fails if the API only supports JSON
curl -H "Accept: application/xml" https://api.example.com/users

# This works
curl -H "Accept: application/json" https://api.example.com/users

Common mistakes:

  • Sending Accept: text/html to a JSON-only API
  • Sending Accept: application/xml to a service that only serves JSON
  • Sending a malformed Accept header ( Accept: json instead of Accept: application/json )
  • Copying a header from documentation that uses a vendor-specific media type ( Accept: application/vnd.api+json ) when the API also accepts standard application/json

The fix: check the API documentation for supported media types. Most REST APIs accept application/json . If you are unsure, use Accept: <em>/</em> to tell the server you will accept any format.

WordPress plugin conflicts

WordPress plugins that modify HTTP response headers can trigger 406 errors. Caching plugins, security plugins, and REST API plugins sometimes set headers that conflict with content negotiation:

  • A caching plugin that strips or modifies Accept headers on cached responses
  • A security plugin that adds strict content-type enforcement
  • A REST API plugin that returns 406 for requests that do not match its expected media types

Diagnose by deactivating plugins one at a time. If the 406 disappears after deactivating a specific plugin, that plugin is the cause. Check the plugin’s settings for content-type or header-related options before removing it entirely.

Browser extensions modifying headers

Extensions that modify request headers can cause 406 errors. Privacy extensions, ad blockers, and developer tools may:

  • Remove the Accept header entirely
  • Replace the Accept header with a non-standard value
  • Add conflicting Accept headers

Test by opening the site in an incognito window with all extensions disabled. If it works in incognito, an extension is modifying the headers. Check the Network tab in DevTools to see the actual request headers being sent.

Strict content negotiation in API frameworks

Some API frameworks enforce strict content negotiation by default. If the framework is configured to return only JSON and the client requests XML, the framework returns 406 rather than falling back to a default format:

Django REST Framework:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ]
}

With only JSONRenderer configured, a request with Accept: text/html returns 406.

Express.js with res.format():

res.format({
    'application/json': () => res.json(data),
    'text/html': () => res.render('page', data),
    default: () => res.status(406).send('Not Acceptable')
});

The default handler explicitly returns 406 for unsupported formats.

Spring Boot:

@GetMapping(value = "/api/users", produces = "application/json")

The produces annotation restricts the endpoint to JSON. Requests with incompatible Accept headers receive 406.

Streaming services (Hulu, Netflix)

A frequently searched variant: “Hulu 406 error.” Streaming services return 406 when their DRM or content delivery system rejects the client. This is not a content negotiation issue in the traditional sense – the service uses 406 to indicate the client is not acceptable for content delivery, which could mean:

  • The geographic region is blocked
  • The device or browser is not supported for DRM playback
  • An ad blocker or VPN is interfering with the content delivery pipeline

This is a client-side restriction, not a server configuration issue. Try disabling VPN, ad blockers, and browser extensions. If using a browser, try a different one. If the issue persists, contact the service’s support.

How to diagnose#

Step 1 – Check the request headers

Open browser DevTools (Network tab) and find the failed request. Look at the request headers, specifically Accept :

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

A normal browser sends a broad Accept header that includes <em>/</em> as a fallback. If the Accept header is narrow or unusual, something is modifying it.

Step 2 – Reproduce with curl

Test with curl to isolate the issue from browser extensions:

# Send with a standard Accept header
curl -v -H "Accept: text/html,application/xhtml+xml,*/*" https://example.com/page

# Send with no Accept header (curl's default is */*)
curl -v https://example.com/page

# Send with a specific Accept header to test strict negotiation
curl -v -H "Accept: application/json" https://example.com/page

If curl works but the browser does not, the issue is browser-specific (extension, cached headers, or modified request).

Step 3 – Check the server error log

# Nginx
tail -20 /var/log/nginx/error.log

# Apache with ModSecurity
tail -50 /var/log/modsec_audit.log

# Application logs
tail -50 /var/log/app/error.log

Look for 406-specific entries or WAF rule triggers.

Step 4 – Test in incognito

An incognito window disables most extensions and starts with default headers. If the 406 disappears in incognito, the cause is a browser extension.

Step 5 – Check if it is cached

If the 406 is intermittent, a caching layer might be serving a cached error response. Purge the relevant cache and test again:

# Test with cache bypass
curl -v -H "Cache-Control: no-cache" https://example.com/page

Fixing a 406#

Fix WAF false positives

If ModSecurity or another WAF is returning 406:

  1. Find the rule ID in the audit log
  2. Verify it is a false positive (the request is legitimate)
  3. Disable the specific rule or create an exception for the affected path
# Disable a specific rule
SecRuleRemoveById 920420

# Or create a path-specific exception
<Location /api/>
    SecRuleRemoveById 920420
</Location>

Fix the Accept header

For API clients:

# Python requests
response = requests.get('https://api.example.com/data', 
    headers={'Accept': 'application/json'})

# JavaScript fetch
fetch('https://api.example.com/data', {
    headers: { 'Accept': 'application/json' }
})

# curl
curl -H "Accept: application/json" https://api.example.com/data

If you do not know what format the API supports, use Accept: <em>/</em> and check the Content-Type header in the response to see what format the server returned.

Fix server-side content negotiation

If your application returns 406 when it should not, add a fallback format:

Django REST Framework:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ]
}

Express.js:

res.format({
    'application/json': () => res.json(data),
    default: () => res.json(data)  // fallback to JSON instead of 406
});

Fix caching issues

If a cache is serving 406 responses, add the Vary header to tell caches that responses vary by Accept header:

# Nginx
add_header Vary Accept;

# Apache
Header append Vary Accept

This ensures the cache stores separate responses for different Accept header values instead of serving an HTML-cached response to a JSON request.

Status codeWhat happened
400 Bad RequestThe request is malformed – bad syntax or encoding
403 ForbiddenAccess denied – you do not have permission
406 Not AcceptableThe server cannot produce a response in the format you requested
415 Unsupported Media TypeThe server cannot process the format you sent (request body)
422 Unprocessable EntityThe request is valid but the data fails validation

The key distinction between 406 and 415: a 406 is about the response format (what the server sends back). A 415 is about the request format (what the client sends in the body). If the client sends JSON but the server only accepts XML, that is 415. If the client wants XML back but the server only produces JSON, that is 406.

Summary#

A 406 Not Acceptable means the server and client cannot agree on a response format. In production, this is most often caused by a WAF rule blocking the request based on the Accept header, not by a genuine content negotiation failure. Check the WAF audit log first. For API calls, verify the Accept header matches the API’s supported formats. For browser issues, test in incognito to rule out extensions. For streaming service 406 errors, disable VPN and ad blockers.