[{"data":1,"prerenderedAt":1186},["ShallowReactive",2],{"content:\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns":3,"surroundings:\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns":1177},{"id":4,"title":5,"body":6,"description":1156,"extension":1157,"meta":1158,"navigation":1170,"path":1171,"seo":1172,"stem":1175,"__hash__":1176},"content\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns\u002Findex.md","Enabling Brotli on nginx and CDNs",{"type":7,"value":8,"toc":1138},"minimark",[9,14,29,37,126,131,171,175,181,191,197,206,210,215,381,387,391,413,502,506,509,513,519,634,638,707,721,725,738,884,888,926,930,939,943,971,975,981,987,993,999,1003,1015,1027,1036,1045,1054,1063,1074,1083,1096,1100,1123,1128,1131,1134],[10,11,13],"h1",{"id":12},"how-to-enable-brotli-on-nginx-and-cdns","How to Enable Brotli on nginx and CDNs",[15,16,17,18,23,24,28],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002F","Compression: Brotli & Zstandard",", within ",[19,25,27],{"href":26},"\u002Fnetwork-protocol-optimization\u002F","Network & Server Response Optimization",". Brotli typically compresses web text 15–25% better than gzip, and every modern browser supports it over HTTPS. Yet many sites still serve gzip, because Brotli is not enabled by default in nginx, needs a module on some platforms, is disabled for certain content types on CDNs, or is lost when an origin's Brotli response passes through a proxy that only knows gzip.",[15,30,31,32,36],{},"Enabling Brotli is mostly configuration, but the details matter: which MIME types to compress, which level to use for dynamic responses, whether to serve precompressed files, how to keep ",[33,34,35],"code",{},"Vary: Accept-Encoding"," correct, and how to make sure the CDN does not decompress and recompress with gzip.",[15,38,39],{},[40,41,47,48,47,55,47,59,47,62,47,71,47,77,47,84,47,92,47,97,47,101,47,107,47,111,47,115,47,122,47],"svg",{"viewBox":42,"width":43,"role":44,"ariaLabel":45,"style":46},"0 0 760 163","100%","img","Bar chart comparing total transfer size of HTML, CSS and JavaScript on a homepage with gzip versus Brotli.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[49,50],"rect",{"className":51,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],"svg-canvas","0","#ffffff",[56,57,58],"title",{},"Transfer size of a news homepage (text resources)",[60,61,45],"desc",{},[49,63],{"x":64,"y":64,"width":65,"height":66,"rx":67,"fill":68,"stroke":69,"style":70},"1","758","161","10","none","currentColor","stroke-opacity:0.18",[72,73,58],"text",{"x":74,"y":75,"fill":69,"style":76},"28.0","34.0","font-size:16px;font-weight:700",[72,78,83],{"x":79,"y":80,"fill":69,"style":81,"textAnchor":82},"186.3","70.0","font-size:13px","end","gzip 6 (on the fly)",[49,85],{"x":86,"y":87,"width":88,"height":89,"rx":90,"fill":68,"stroke":69,"style":91},"198.3","56.0","469.7","19","3","fill-opacity:0.7;stroke-opacity:0.35",[72,93,96],{"x":94,"y":80,"fill":69,"style":95},"674.0","font-size:12px;font-weight:600","612KB",[72,98,100],{"x":79,"y":99,"fill":69,"style":81,"textAnchor":82},"101.0","Brotli 5 (on the fly)",[49,102],{"x":86,"y":103,"width":104,"height":89,"rx":90,"fill":105,"stroke":105,"style":106},"87.0","405.2","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[72,108,110],{"x":109,"y":99,"fill":69,"style":95},"609.5","528KB",[72,112,114],{"x":79,"y":113,"fill":69,"style":81,"textAnchor":82},"132.0","Brotli 11 static + 5 dynamic",[49,116],{"x":86,"y":117,"width":118,"height":89,"rx":90,"fill":119,"stroke":120,"style":121},"118.0","375.3","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[72,123,125],{"x":124,"y":113,"fill":69,"style":95},"579.6","489KB",[127,128,130],"h2",{"id":129},"rapid-diagnosis","Rapid Diagnosis",[132,133,134,145,159,165],"ul",{},[135,136,137,144],"li",{},[138,139,140,141],"strong",{},"Check ",[33,142,143],{},"Content-Encoding"," on HTML, CSS and JS in a modern browser's Network panel.",[135,146,147,150,151,154,155,158],{},[138,148,149],{},"Request with explicit headers",": ",[33,152,153],{},"curl -sI -H 'Accept-Encoding: br' https:\u002F\u002Fexample.com\u002F"," and look for ",[33,156,157],{},"content-encoding: br",".",[135,160,161,164],{},[138,162,163],{},"Check the CDN dashboard"," for compression settings and supported content types.",[135,166,167,170],{},[138,168,169],{},"Check origin vs edge",": request the origin directly to see whether Brotli is lost at the CDN.",[127,172,174],{"id":173},"root-cause-analysis","Root Cause Analysis",[15,176,177,180],{},[138,178,179],{},"1. Not enabled."," nginx needs the Brotli module; many default configs only enable gzip.",[15,182,183,186,187,190],{},[138,184,185],{},"2. MIME types missing."," JSON, SVG, XML or ",[33,188,189],{},"text\u002Fjavascript"," omitted from the compressible list.",[15,192,193,196],{},[138,194,195],{},"3. CDN normalisation."," The CDN serves gzip because its compression setting or cache key does not include Brotli.",[15,198,199,202,203,158],{},[138,200,201],{},"4. Proxies."," Intermediate proxies strip or rewrite ",[33,204,205],{},"Accept-Encoding",[127,207,209],{"id":208},"step-by-step-resolution","Step-by-Step Resolution",[211,212,214],"h3",{"id":213},"_1-nginx-with-the-brotli-module","1. nginx with the Brotli module",[216,217,222],"pre",{"className":218,"code":219,"language":220,"meta":221,"style":221},"language-nginx shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Requires ngx_brotli (packaged on many distributions, or built as a dynamic module).\nload_module modules\u002Fngx_http_brotli_filter_module.so;\nload_module modules\u002Fngx_http_brotli_static_module.so;\nhttp {\n  brotli on;\n  brotli_comp_level 5;\n  brotli_min_length 1024;\n  brotli_types text\u002Fplain text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson\n               application\u002Fxml image\u002Fsvg+xml application\u002Fmanifest+json font\u002Fttf font\u002Fotf;\n  brotli_static on;                     # serve file.br when present\n  gzip on; gzip_static on; gzip_vary on;\n  gzip_types text\u002Fplain text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson application\u002Fxml image\u002Fsvg+xml;\n}\n# trade-off: brotli_comp_level 5 balances CPU and size for dynamic responses;\n# static assets should be precompressed at 11 and served via brotli_static.\n","nginx","",[33,223,224,233,244,252,261,274,285,296,305,314,328,354,363,369,375],{"__ignoreMap":221},[225,226,229],"span",{"class":227,"line":228},"line",1,[225,230,232],{"class":231},"sjfSM","# Requires ngx_brotli (packaged on many distributions, or built as a dynamic module).\n",[225,234,236,240],{"class":227,"line":235},2,[225,237,239],{"class":238},"sPARh","load_module ",[225,241,243],{"class":242},"saISM","modules\u002Fngx_http_brotli_filter_module.so;\n",[225,245,247,249],{"class":227,"line":246},3,[225,248,239],{"class":238},[225,250,251],{"class":242},"modules\u002Fngx_http_brotli_static_module.so;\n",[225,253,255,258],{"class":227,"line":254},4,[225,256,257],{"class":238},"http",[225,259,260],{"class":242}," {\n",[225,262,264,267,271],{"class":227,"line":263},5,[225,265,266],{"class":238},"  brotli",[225,268,270],{"class":269},"sPXB4"," on",[225,272,273],{"class":242},";\n",[225,275,277,280,283],{"class":227,"line":276},6,[225,278,279],{"class":238},"  brotli_comp_level",[225,281,282],{"class":269}," 5",[225,284,273],{"class":242},[225,286,288,291,294],{"class":227,"line":287},7,[225,289,290],{"class":238},"  brotli_min_length",[225,292,293],{"class":269}," 1024",[225,295,273],{"class":242},[225,297,299,302],{"class":227,"line":298},8,[225,300,301],{"class":238},"  brotli_types",[225,303,304],{"class":242}," text\u002Fplain text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson\n",[225,306,308,311],{"class":227,"line":307},9,[225,309,310],{"class":269},"               application\u002Fxml",[225,312,313],{"class":242}," image\u002Fsvg+xml application\u002Fmanifest+json font\u002Fttf font\u002Fotf;\n",[225,315,317,320,322,325],{"class":227,"line":316},10,[225,318,319],{"class":238},"  brotli_static",[225,321,270],{"class":269},[225,323,324],{"class":242},";                     ",[225,326,327],{"class":231},"# serve file.br when present\n",[225,329,331,334,337,340,343,345,347,350,352],{"class":227,"line":330},11,[225,332,333],{"class":238},"  gzip ",[225,335,336],{"class":269},"on",[225,338,339],{"class":242},";",[225,341,342],{"class":238}," gzip_static ",[225,344,336],{"class":269},[225,346,339],{"class":242},[225,348,349],{"class":238}," gzip_vary ",[225,351,336],{"class":269},[225,353,273],{"class":242},[225,355,357,360],{"class":227,"line":356},12,[225,358,359],{"class":238},"  gzip_types ",[225,361,362],{"class":242},"text\u002Fplain text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson application\u002Fxml image\u002Fsvg+xml;\n",[225,364,366],{"class":227,"line":365},13,[225,367,368],{"class":242},"}\n",[225,370,372],{"class":227,"line":371},14,[225,373,374],{"class":231},"# trade-off: brotli_comp_level 5 balances CPU and size for dynamic responses;\n",[225,376,378],{"class":227,"line":377},15,[225,379,380],{"class":231},"# static assets should be precompressed at 11 and served via brotli_static.\n",[15,382,383,386],{},[33,384,385],{},"text\u002Fhtml"," is always compressed when compression is on and does not need listing.",[211,388,390],{"id":389},"_2-apache-and-nodejs","2. Apache and Node.js",[216,392,396],{"className":393,"code":394,"language":395,"meta":221,"style":221},"language-apache shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Apache (mod_brotli)\nAddOutputFilterByType BROTLI_COMPRESS text\u002Fhtml text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson image\u002Fsvg+xml\nBrotliCompressionQuality 5\n","apache",[33,397,398,403,408],{"__ignoreMap":221},[225,399,400],{"class":227,"line":228},[225,401,402],{},"# Apache (mod_brotli)\n",[225,404,405],{"class":227,"line":235},[225,406,407],{},"AddOutputFilterByType BROTLI_COMPRESS text\u002Fhtml text\u002Fcss text\u002Fjavascript application\u002Fjavascript application\u002Fjson image\u002Fsvg+xml\n",[225,409,410],{"class":227,"line":246},[225,411,412],{},"BrotliCompressionQuality 5\n",[216,414,418],{"className":415,"code":416,"language":417,"meta":221,"style":221},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Node.js (Express) with the compression middleware supporting Brotli.\nimport compression from 'compression';\nimport { constants } from 'node:zlib';\napp.use(compression({ brotli: { params: { [constants.BROTLI_PARAM_QUALITY]: 5 } }, threshold: 1024 }));\n\u002F\u002F trade-off: in-process compression uses the same CPU as rendering; at high load,\n\u002F\u002F offload compression to a reverse proxy or the CDN.\n","javascript",[33,419,420,425,442,456,492,497],{"__ignoreMap":221},[225,421,422],{"class":227,"line":228},[225,423,424],{"class":231},"\u002F\u002F Node.js (Express) with the compression middleware supporting Brotli.\n",[225,426,427,430,433,436,440],{"class":227,"line":235},[225,428,429],{"class":238},"import",[225,431,432],{"class":242}," compression ",[225,434,435],{"class":238},"from",[225,437,439],{"class":438},"sZ8jY"," 'compression'",[225,441,273],{"class":242},[225,443,444,446,449,451,454],{"class":227,"line":246},[225,445,429],{"class":238},[225,447,448],{"class":242}," { constants } ",[225,450,435],{"class":238},[225,452,453],{"class":438}," 'node:zlib'",[225,455,273],{"class":242},[225,457,458,461,465,468,471,474,477,480,483,486,489],{"class":227,"line":254},[225,459,460],{"class":242},"app.",[225,462,464],{"class":463},"smZ65","use",[225,466,467],{"class":242},"(",[225,469,470],{"class":463},"compression",[225,472,473],{"class":242},"({ brotli: { params: { [constants.",[225,475,476],{"class":269},"BROTLI_PARAM_QUALITY",[225,478,479],{"class":242},"]: ",[225,481,482],{"class":269},"5",[225,484,485],{"class":242}," } }, threshold: ",[225,487,488],{"class":269},"1024",[225,490,491],{"class":242}," }));\n",[225,493,494],{"class":227,"line":263},[225,495,496],{"class":231},"\u002F\u002F trade-off: in-process compression uses the same CPU as rendering; at high load,\n",[225,498,499],{"class":227,"line":276},[225,500,501],{"class":231},"\u002F\u002F offload compression to a reverse proxy or the CDN.\n",[211,503,505],{"id":504},"_3-cdns","3. CDNs",[15,507,508],{},"Most CDNs support Brotli: some compress at the edge automatically for eligible content types; others pass through origin Brotli responses when the cache key includes the encoding. Enable Brotli in the CDN settings, check the list of compressible content types, and ensure the origin's precompressed Brotli responses are not decompressed.",[211,510,512],{"id":511},"_4-verify-vary-and-caching","4. Verify Vary and caching",[15,514,515,516,518],{},"Responses that vary by encoding must include ",[33,517,35],{}," (or the CDN must key by encoding), so caches never serve Brotli to clients that cannot decode it.",[15,520,521],{},[40,522,47,525,47,528,47,531,47,533,47,549,47,552,47,554,47,560,47,567,47,570,47,574,47,577,47,581,47,589,47,592,47,595,47,601,47,607,47,611,47,616,47,621,47,625,47,630,47],{"viewBox":523,"width":43,"role":44,"ariaLabel":524,"style":46},"0 0 760 290","Sequence showing a browser requesting Brotli, the CDN forwarding to the origin, and caching the Brotli variant.",[49,526],{"className":527,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],[56,529,530],{},"Brotli negotiation through a CDN",[60,532,524],{},[534,535,536],"defs",{},[537,538,544],"marker",{"id":539,"viewBox":540,"refX":541,"refY":482,"markerWidth":542,"markerHeight":542,"orient":543},"facdd827c5","0 0 10 10","9","7","auto-start-reverse",[545,546],"path",{"d":547,"fill":69,"style":548},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[49,550],{"x":64,"y":64,"width":65,"height":551,"rx":67,"fill":68,"stroke":69,"style":70},"288",[72,553,530],{"x":74,"y":75,"fill":69,"style":76},[49,555],{"x":556,"y":87,"width":557,"height":75,"rx":558,"fill":69,"stroke":69,"style":559},"70.3","150.0","6","fill-opacity:0.06;stroke-opacity:0.4",[72,561,566],{"x":562,"y":563,"fill":69,"style":564,"textAnchor":565},"145.3","78.0","font-size:13px;font-weight:700","middle","Browser",[49,568],{"x":569,"y":87,"width":557,"height":75,"rx":558,"fill":69,"stroke":69,"style":559},"305.0",[72,571,573],{"x":572,"y":563,"fill":69,"style":564,"textAnchor":565},"380.0","CDN edge",[49,575],{"x":576,"y":87,"width":557,"height":75,"rx":558,"fill":69,"stroke":69,"style":559},"539.7",[72,578,580],{"x":579,"y":563,"fill":69,"style":564,"textAnchor":565},"614.7","Origin",[227,582],{"x1":562,"y1":583,"x2":562,"y2":584,"stroke":69,"strokeWidth":585,"strokeDashArray":586,"style":588},"90.0","268.0","1.5",[587,587],"4","stroke-opacity:0.35",[227,590],{"x1":572,"y1":583,"x2":572,"y2":584,"stroke":69,"strokeWidth":585,"strokeDashArray":591,"style":588},[587,587],[227,593],{"x1":579,"y1":583,"x2":579,"y2":584,"stroke":69,"strokeWidth":585,"strokeDashArray":594,"style":588},[587,587],[227,596],{"x1":562,"y1":597,"x2":598,"y2":597,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":600},"124.0","378.0","stroke-opacity:0.6","url(#facdd827c5)",[72,602,606],{"x":603,"y":604,"fill":69,"style":605,"textAnchor":565},"262.7","117.0","font-size:12px","Accept-Encoding: br, gzip",[227,608],{"x1":572,"y1":609,"x2":610,"y2":609,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":600},"164.0","612.7",[72,612,615],{"x":613,"y":614,"fill":69,"style":605,"textAnchor":565},"497.3","157.0","forward with br",[227,617],{"x1":579,"y1":618,"x2":619,"y2":618,"stroke":69,"strokeWidth":585,"strokeDashArray":620,"style":599,"markerEnd":600},"204.0","382.0",[482,587],[72,622,624],{"x":613,"y":623,"fill":69,"style":605,"textAnchor":565},"197.0","br body + Vary",[227,626],{"x1":572,"y1":627,"x2":628,"y2":627,"stroke":69,"strokeWidth":585,"strokeDashArray":629,"style":599,"markerEnd":600},"244.0","147.3",[482,587],[72,631,633],{"x":603,"y":632,"fill":69,"style":605,"textAnchor":565},"237.0","br response (cached per encoding)",[127,635,637],{"id":636},"verification","Verification",[216,639,643],{"className":640,"code":641,"language":642,"meta":221,"style":221},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","curl -sI -H 'Accept-Encoding: br, gzip' https:\u002F\u002Fexample.com\u002Fjs\u002Fapp.4f2a.js | grep -iE 'content-encoding|vary|content-length'\ncurl -sI -H 'Accept-Encoding: gzip' https:\u002F\u002Fexample.com\u002Fjs\u002Fapp.4f2a.js | grep -iE 'content-encoding'\n# trade-off: HEAD requests may be answered differently by some servers; if in\n# doubt, use GET with -o \u002Fdev\u002Fnull and -D - to print headers.\n","bash",[33,644,645,675,697,702],{"__ignoreMap":221},[225,646,647,651,654,657,660,663,666,669,672],{"class":227,"line":228},[225,648,650],{"class":649},"sQw3B","curl",[225,652,653],{"class":269}," -sI",[225,655,656],{"class":269}," -H",[225,658,659],{"class":438}," 'Accept-Encoding: br, gzip'",[225,661,662],{"class":438}," https:\u002F\u002Fexample.com\u002Fjs\u002Fapp.4f2a.js",[225,664,665],{"class":238}," |",[225,667,668],{"class":649}," grep",[225,670,671],{"class":269}," -iE",[225,673,674],{"class":438}," 'content-encoding|vary|content-length'\n",[225,676,677,679,681,683,686,688,690,692,694],{"class":227,"line":235},[225,678,650],{"class":649},[225,680,653],{"class":269},[225,682,656],{"class":269},[225,684,685],{"class":438}," 'Accept-Encoding: gzip'",[225,687,662],{"class":438},[225,689,665],{"class":238},[225,691,668],{"class":649},[225,693,671],{"class":269},[225,695,696],{"class":438}," 'content-encoding'\n",[225,698,699],{"class":227,"line":246},[225,700,701],{"class":231},"# trade-off: HEAD requests may be answered differently by some servers; if in\n",[225,703,704],{"class":227,"line":254},[225,705,706],{"class":231},"# doubt, use GET with -o \u002Fdev\u002Fnull and -D - to print headers.\n",[15,708,709,710,713,714,717,718,720],{},"The first should return ",[33,711,712],{},"br",", the second ",[33,715,716],{},"gzip",". In the browser, the Network panel's \"Content-Encoding\" column (right-click column headers to add it) should show ",[33,719,712],{}," for text resources.",[127,722,724],{"id":723},"worked-example-a-media-site-on-nginx-behind-a-cdn","Worked Example: A Media Site on nginx Behind a CDN",[15,726,727,728,730,731,733,734,737],{},"A media site's nginx origin had Brotli enabled, but the CDN served gzip to every user. The CDN's cache key ignored ",[33,729,205],{},", so it requested the origin with only ",[33,732,716],{}," to stay safe. Switching on the CDN's Brotli support (which keyed variants by a normalised encoding value) made Brotli reach users. Text transfer per page view fell by 14%, and LCP p75 on mobile improved by about 120ms. A follow-up added ",[33,735,736],{},"brotli_static"," with precompressed assets, saving another 6% and reducing origin CPU.",[15,739,740],{},[40,741,47,744,47,747,47,750,47,752,47,759,47,762,47,764,47,768,47,772,47,776,47,781,47,787,47,792,47,796,47,802,47,805,47,809,47,811,47,814,47,817,47,820,47,823,47,826,47,829,47,833,47,835,47,838,47,841,47,844,47,848,47,851,47,854,47,858,47,860,47,863,47,866,47,869,47,873,47,876,47,880,47],{"viewBox":742,"width":43,"role":44,"ariaLabel":743,"style":46},"0 0 760 396","Decision sequence for diagnosing why a site serves gzip instead of Brotli.",[49,745],{"className":746,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],[56,748,749],{},"Why am I still getting gzip?",[60,751,743],{},[534,753,754],{},[537,755,757],{"id":756,"viewBox":540,"refX":541,"refY":482,"markerWidth":542,"markerHeight":542,"orient":543},"fa3c67294b",[545,758],{"d":547,"fill":69,"style":548},[49,760],{"x":64,"y":64,"width":65,"height":761,"rx":67,"fill":68,"stroke":69,"style":70},"394",[72,763,749],{"x":74,"y":75,"fill":69,"style":76},[49,765],{"x":74,"y":87,"width":766,"height":767,"rx":558,"fill":69,"stroke":69,"style":559},"320.0","35.0",[72,769,771],{"x":770,"y":563,"fill":69,"style":564,"textAnchor":565},"188.0","Is the request over plain HTTP?",[49,773],{"x":774,"y":87,"width":766,"height":767,"rx":558,"fill":105,"stroke":105,"style":775},"412.0","fill-opacity:0.14;stroke-opacity:0.9",[72,777,780],{"x":778,"y":563,"fill":69,"style":779,"textAnchor":565},"572.0","font-size:12.5px","Browsers only offer br over HTTPS",[227,782],{"x1":783,"y1":784,"x2":785,"y2":784,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"348.0","73.5","410.0","url(#fa3c67294b)",[72,788,791],{"x":572,"y":789,"fill":105,"style":790,"textAnchor":565},"66.5","font-size:11.5px;font-weight:700","yes",[227,793],{"x1":770,"y1":794,"x2":770,"y2":795,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"91.0","125.0",[72,797,801],{"x":798,"y":799,"fill":800,"style":790},"196.0","113.0","#51617a","no",[49,803],{"x":74,"y":804,"width":766,"height":767,"rx":558,"fill":69,"stroke":69,"style":559},"127.0",[72,806,808],{"x":770,"y":807,"fill":69,"style":564,"textAnchor":565},"149.0","Does the origin return br when requested directly?",[49,810],{"x":774,"y":804,"width":766,"height":767,"rx":558,"fill":68,"stroke":69,"style":588},[72,812,813],{"x":778,"y":807,"fill":69,"style":779,"textAnchor":565},"Enable ngx_brotli or server support",[227,815],{"x1":783,"y1":816,"x2":785,"y2":816,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"144.5",[72,818,791],{"x":572,"y":819,"fill":105,"style":790,"textAnchor":565},"137.5",[227,821],{"x1":770,"y1":822,"x2":770,"y2":798,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"162.0",[72,824,801],{"x":798,"y":825,"fill":800,"style":790},"184.0",[49,827],{"x":74,"y":828,"width":766,"height":767,"rx":558,"fill":69,"stroke":69,"style":559},"198.0",[72,830,832],{"x":770,"y":831,"fill":69,"style":564,"textAnchor":565},"220.0","Does the CDN return gzip for the same request?",[49,834],{"x":774,"y":828,"width":766,"height":767,"rx":558,"fill":105,"stroke":105,"style":775},[72,836,837],{"x":778,"y":831,"fill":69,"style":779,"textAnchor":565},"Enable CDN Brotli or pass-through",[227,839],{"x1":783,"y1":840,"x2":785,"y2":840,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"215.5",[72,842,791],{"x":572,"y":843,"fill":105,"style":790,"textAnchor":565},"208.5",[227,845],{"x1":770,"y1":846,"x2":770,"y2":847,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"233.0","267.0",[72,849,801],{"x":798,"y":850,"fill":800,"style":790},"255.0",[49,852],{"x":74,"y":853,"width":766,"height":767,"rx":558,"fill":69,"stroke":69,"style":559},"269.0",[72,855,857],{"x":770,"y":856,"fill":69,"style":564,"textAnchor":565},"291.0","Is the content type in brotli_types?",[49,859],{"x":774,"y":853,"width":766,"height":767,"rx":558,"fill":105,"stroke":105,"style":775},[72,861,862],{"x":778,"y":856,"fill":69,"style":779,"textAnchor":565},"Add the missing MIME type",[227,864],{"x1":783,"y1":865,"x2":785,"y2":865,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"286.5",[72,867,791],{"x":572,"y":868,"fill":105,"style":790,"textAnchor":565},"279.5",[227,870],{"x1":770,"y1":871,"x2":770,"y2":872,"stroke":69,"strokeWidth":585,"style":599,"markerEnd":786},"304.0","338.0",[72,874,801],{"x":798,"y":875,"fill":800,"style":790},"326.0",[49,877],{"x":74,"y":878,"width":766,"height":75,"rx":558,"fill":119,"stroke":120,"style":879},"340.0","fill-opacity:0.24;stroke-opacity:0.9",[72,881,883],{"x":770,"y":882,"fill":69,"style":779,"textAnchor":565},"361.5","Check proxies that rewrite Accept-Encoding",[127,885,887],{"id":886},"content-types-people-forget","Content Types People Forget",[15,889,890,891,894,895,898,899,902,903,906,907,910,911,913,914,917,918,921,922,925],{},"Default compression lists often miss: ",[33,892,893],{},"application\u002Fjson"," (API responses), ",[33,896,897],{},"application\u002Fmanifest+json"," (web app manifests), ",[33,900,901],{},"image\u002Fsvg+xml"," (SVG icons and illustrations), ",[33,904,905],{},"application\u002Fxml"," and ",[33,908,909],{},"text\u002Fxml"," (sitemaps, feeds), ",[33,912,189],{}," (the now-standard JavaScript MIME type), ",[33,915,916],{},"application\u002Fwasm"," (WebAssembly, which compresses moderately), and uncompressed font formats (",[33,919,920],{},"font\u002Fttf",", ",[33,923,924],{},"font\u002Fotf","). WOFF2 is already compressed and should be excluded. Check each content type your site serves against the list.",[127,927,929],{"id":928},"testing-brotli-across-the-delivery-chain","Testing Brotli Across the Delivery Chain",[15,931,932,933,935,936,938],{},"Brotli can be lost at any hop, so test each one. Request the origin directly (bypassing the CDN with its internal hostname or a hosts-file override) and confirm ",[33,934,157],{},". Then request through the CDN from a few locations. If an internal load balancer or ingress controller sits in front of the application, request through it too; some ingress controllers decompress and recompress or strip ",[33,937,205],{},". Record the transferred size at each hop for one known asset; where it jumps, the hop is recompressing or not compressing.",[127,940,942],{"id":941},"common-mistakes","Common Mistakes",[132,944,945,951,957,965],{},[135,946,947,950],{},[138,948,949],{},"Enabling Brotli at the origin but not the CDN."," Users still receive gzip.",[135,952,953,956],{},[138,954,955],{},"Using level 11 for dynamic responses."," Adds latency to every request.",[135,958,959,964],{},[138,960,961,962,158],{},"Missing ",[33,963,35],{}," Risk of serving undecodable responses from caches.",[135,966,967,970],{},[138,968,969],{},"Compressing WOFF2, images or video."," No benefit, extra CPU.",[127,972,974],{"id":973},"edge-cases","Edge Cases",[15,976,977,980],{},[138,978,979],{},"HTTP (not HTTPS)."," Browsers only advertise Brotli over HTTPS.",[15,982,983,986],{},[138,984,985],{},"Old clients and bots."," Keep gzip enabled for clients that do not support Brotli.",[15,988,989,992],{},[138,990,991],{},"Server-Sent Events."," Compression can buffer event streams; disable or configure flushing for SSE endpoints.",[15,994,995,998],{},[138,996,997],{},"Range requests."," Compressed responses and byte ranges interact poorly; serve large media uncompressed.",[127,1000,1002],{"id":1001},"faq","FAQ",[1004,1005,1008,1012],"details",{"className":1006},[1007],"faq-item",[1009,1010,1011],"summary",{},"Does nginx support Brotli natively?",[15,1013,1014],{},"Not in the core build. The ngx_brotli module adds it and is available as a package on many distributions or as a dynamic module.",[1004,1016,1018,1021],{"className":1017},[1007],[1009,1019,1020],{},"What Brotli level should I use on nginx?",[15,1022,1023,1024,1026],{},"Around 4–6 for on-the-fly compression. Use ",[33,1025,736],{}," to serve precompressed level-11 files for static assets.",[1004,1028,1030,1033],{"className":1029},[1007],[1009,1031,1032],{},"Will Brotli increase server CPU usage?",[15,1034,1035],{},"At moderate levels, slightly compared with gzip. Precompressing static assets removes most of the on-the-fly work.",[1004,1037,1039,1042],{"className":1038},[1007],[1009,1040,1041],{},"Does my CDN need special settings for Brotli?",[15,1043,1044],{},"Usually an option to enable Brotli and a list of content types. Check whether it compresses at the edge, passes through origin Brotli, or both.",[1004,1046,1048,1051],{"className":1047},[1007],[1009,1049,1050],{},"Should I disable gzip when Brotli is enabled?",[15,1052,1053],{},"No. Keep gzip for clients that do not support Brotli, such as some older browsers, tools and crawlers.",[1004,1055,1057,1060],{"className":1056},[1007],[1009,1058,1059],{},"Is Brotli worth it for small responses?",[15,1061,1062],{},"Below about 1KB, savings are small and overhead can dominate. Set a minimum length.",[1004,1064,1066,1069],{"className":1065},[1007],[1009,1067,1068],{},"How do I check Brotli in Chrome DevTools?",[15,1070,1071,1072,158],{},"Add the Content-Encoding column in the Network panel, or select a request and check the Response Headers for ",[33,1073,157],{},[1004,1075,1077,1080],{"className":1076},[1007],[1009,1078,1079],{},"Does Brotli work with HTTP\u002F3?",[15,1081,1082],{},"Yes. Content encoding is independent of the transport protocol, so Brotli responses work the same over HTTP\u002F1.1, HTTP\u002F2 and HTTP\u002F3.",[1004,1084,1086,1089],{"className":1085},[1007],[1009,1087,1088],{},"Can I enable Brotli only for some paths?",[15,1090,1091,1092,1095],{},"Yes. In nginx, the directives work per ",[33,1093,1094],{},"location",", so you can enable Brotli for HTML and assets while leaving streaming endpoints untouched.",[127,1097,1099],{"id":1098},"related","Related",[132,1101,1102,1109,1116],{},[135,1103,1104,1108],{},[19,1105,1107],{"href":1106},"\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fprecompressing-static-assets-at-build-time\u002F","Precompressing static assets at build time"," — level-11 files for brotli_static.",[135,1110,1111,1115],{},[19,1112,1114],{"href":1113},"\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fzstandard-content-encoding-for-the-web\u002F","Zstandard Content-Encoding for the web"," — the faster alternative for dynamic content.",[135,1117,1118,1122],{},[19,1119,1121],{"href":1120},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fcdn-edge-caching-configuration\u002Fvary-header-pitfalls-that-destroy-cdn-hit-rate\u002F","Vary header pitfalls that destroy CDN hit rate"," — encoding variants and caches.",[1124,1125,1127],"script",{"type":1126},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Enable Brotli on nginx and CDNs\",\n  \"description\": \"Step-by-step configuration for Brotli compression on common servers and CDNs, with the settings that matter and how to verify them.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"nginx with the Brotli module\",\n      \"text\": \"text\u002Fhtml is always compressed when compression is on and does not need listing.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Apache and Node.js\",\n      \"text\": \"Apache and Node.js\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"CDNs\",\n      \"text\": \"Most CDNs support Brotli: some compress at the edge automatically for eligible content types; others pass through origin Brotli responses when the cache key includes the encoding.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Verify Vary and caching\",\n      \"text\": \"Responses that vary by encoding must include Vary: Accept-Encoding (or the CDN must key by encoding), so caches never serve Brotli to clients that cannot decode it.\"\n    }\n  ]\n}\n",[1124,1129,1130],{"type":1126},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Enable Brotli on nginx and CDNs\",\n  \"description\": \"Step-by-step configuration for Brotli compression on common servers and CDNs, with the settings that matter and how to verify them.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns\u002F\"\n  }\n}\n",[1124,1132,1133],{"type":1126},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Network & Server Response Optimization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Compression: Brotli & Zstandard\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Enabling Brotli on nginx and CDNs\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns\u002F\"\n    }\n  ]\n}\n",[1135,1136,1137],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":221,"searchDepth":235,"depth":235,"links":1139},[1140,1141,1142,1148,1149,1150,1151,1152,1153,1154,1155],{"id":129,"depth":235,"text":130},{"id":173,"depth":235,"text":174},{"id":208,"depth":235,"text":209,"children":1143},[1144,1145,1146,1147],{"id":213,"depth":246,"text":214},{"id":389,"depth":246,"text":390},{"id":504,"depth":246,"text":505},{"id":511,"depth":246,"text":512},{"id":636,"depth":235,"text":637},{"id":723,"depth":235,"text":724},{"id":886,"depth":235,"text":887},{"id":928,"depth":235,"text":929},{"id":941,"depth":235,"text":942},{"id":973,"depth":235,"text":974},{"id":1001,"depth":235,"text":1002},{"id":1098,"depth":235,"text":1099},"Step-by-step configuration for Brotli compression on common servers and CDNs, with the settings that matter and how to verify them.","md",{"slug":1159,"type":1160,"breadcrumb":1161,"datePublished":1169,"dateModified":1169},"enabling-brotli-on-nginx-and-cdns","article",[1162,1165,1166,1167],{"name":1163,"url":1164},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1168},"\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns\u002F","2026-10-06",true,"\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns",{"title":1173,"description":1174},"Enabling Brotli Compression on nginx and CDNs","Turn on Brotli for HTML, CSS, JS and JSON on nginx, Apache, Node and major CDNs: modules, levels, MIME types, Vary headers and verifying Content-Encoding.","network-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fenabling-brotli-on-nginx-and-cdns\u002Findex","z_OXmzho_olNNWN-gJw2-vfwOckGudWZHvXH1wiq20g",[1178,1182],{"title":1179,"path":1180,"stem":1181},"Compression Dictionary Transport","\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fcompression-dictionary-transport","network-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fcompression-dictionary-transport\u002Findex",{"title":1183,"path":1184,"stem":1185},"Precompressing Static Assets at Build Time","\u002Fnetwork-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fprecompressing-static-assets-at-build-time","network-protocol-optimization\u002Fcompression-brotli-and-zstandard\u002Fprecompressing-static-assets-at-build-time\u002Findex",1791308076352]