[{"data":1,"prerenderedAt":1696},["ShallowReactive",2],{"\u002Fdeploy\u002Fflask-static-and-media-files-production-setup":3},{"id":4,"title":5,"body":6,"description":1686,"extension":1687,"meta":1688,"navigation":84,"path":1692,"seo":1693,"stem":1694,"__hash__":1695},"content\u002Fdeploy\u002Fflask-static-and-media-files-production-setup.md","Flask Static and Media Files Production Setup",{"type":7,"value":8,"toc":1669},"minimark",[9,13,17,22,30,274,288,292,295,311,315,950,985,991,998,1028,1032,1039,1042,1049,1052,1059,1062,1085,1089,1187,1191,1194,1199,1226,1229,1252,1256,1285,1287,1309,1313,1347,1349,1367,1371,1392,1394,1412,1416,1443,1445,1456,1460,1550,1554,1581,1585,1593,1601,1609,1617,1625,1637,1645,1653,1657,1665],[10,11,5],"h1",{"id":12},"flask-static-and-media-files-production-setup",[14,15,16],"p",{},"If you're trying to serve Flask static assets and uploaded media files correctly in production, this guide shows you how to set them up step-by-step. The goal is to let Nginx serve files directly, keep Gunicorn focused on the app, and prevent missing asset or upload errors.",[18,19,21],"h2",{"id":20},"quick-fix-quick-setup","Quick Fix \u002F Quick Setup",[14,23,24,25,29],{},"Create dedicated static and media directories, map them in Nginx with ",[26,27,28],"code",{},"alias",", and keep application traffic proxied to Gunicorn.",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-bash shiki shiki-themes github-light github-dark","sudo mkdir -p \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\nsudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyapp\n\n# Example Nginx locations\nsudo tee \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp >\u002Fdev\u002Fnull \u003C\u003C'EOF'\nserver {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002F;\n        access_log off;\n        expires 7d;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n        access_log off;\n        expires 1d;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n        include proxy_params;\n    }\n}\nEOF\n\nsudo nginx -t && sudo systemctl reload nginx\n","bash","",[26,38,39,62,79,86,93,117,123,129,135,140,146,152,158,164,170,175,181,187,192,198,203,208,214,220,226,231,237,243,248],{"__ignoreMap":36},[40,41,44,48,52,56,59],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sScJk","sudo",[40,49,51],{"class":50},"sZZnC"," mkdir",[40,53,55],{"class":54},"sj4cs"," -p",[40,57,58],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic",[40,60,61],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\n",[40,63,65,67,70,73,76],{"class":42,"line":64},2,[40,66,47],{"class":46},[40,68,69],{"class":50}," chown",[40,71,72],{"class":54}," -R",[40,74,75],{"class":50}," www-data:www-data",[40,77,78],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\n",[40,80,82],{"class":42,"line":81},3,[40,83,85],{"emptyLinePlaceholder":84},true,"\n",[40,87,89],{"class":42,"line":88},4,[40,90,92],{"class":91},"sJ8bj","# Example Nginx locations\n",[40,94,96,98,101,104,108,111,114],{"class":42,"line":95},5,[40,97,47],{"class":46},[40,99,100],{"class":50}," tee",[40,102,103],{"class":50}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp",[40,105,107],{"class":106},"szBVR"," >",[40,109,110],{"class":50},"\u002Fdev\u002Fnull",[40,112,113],{"class":106}," \u003C\u003C",[40,115,116],{"class":50},"'EOF'\n",[40,118,120],{"class":42,"line":119},6,[40,121,122],{"class":50},"server {\n",[40,124,126],{"class":42,"line":125},7,[40,127,128],{"class":50},"    listen 80;\n",[40,130,132],{"class":42,"line":131},8,[40,133,134],{"class":50},"    server_name example.com;\n",[40,136,138],{"class":42,"line":137},9,[40,139,85],{"emptyLinePlaceholder":84},[40,141,143],{"class":42,"line":142},10,[40,144,145],{"class":50},"    location \u002Fstatic\u002F {\n",[40,147,149],{"class":42,"line":148},11,[40,150,151],{"class":50},"        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002F;\n",[40,153,155],{"class":42,"line":154},12,[40,156,157],{"class":50},"        access_log off;\n",[40,159,161],{"class":42,"line":160},13,[40,162,163],{"class":50},"        expires 7d;\n",[40,165,167],{"class":42,"line":166},14,[40,168,169],{"class":50},"    }\n",[40,171,173],{"class":42,"line":172},15,[40,174,85],{"emptyLinePlaceholder":84},[40,176,178],{"class":42,"line":177},16,[40,179,180],{"class":50},"    location \u002Fmedia\u002F {\n",[40,182,184],{"class":42,"line":183},17,[40,185,186],{"class":50},"        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n",[40,188,190],{"class":42,"line":189},18,[40,191,157],{"class":50},[40,193,195],{"class":42,"line":194},19,[40,196,197],{"class":50},"        expires 1d;\n",[40,199,201],{"class":42,"line":200},20,[40,202,169],{"class":50},[40,204,206],{"class":42,"line":205},21,[40,207,85],{"emptyLinePlaceholder":84},[40,209,211],{"class":42,"line":210},22,[40,212,213],{"class":50},"    location \u002F {\n",[40,215,217],{"class":42,"line":216},23,[40,218,219],{"class":50},"        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n",[40,221,223],{"class":42,"line":222},24,[40,224,225],{"class":50},"        include proxy_params;\n",[40,227,229],{"class":42,"line":228},25,[40,230,169],{"class":50},[40,232,234],{"class":42,"line":233},26,[40,235,236],{"class":50},"}\n",[40,238,240],{"class":42,"line":239},27,[40,241,242],{"class":50},"EOF\n",[40,244,246],{"class":42,"line":245},28,[40,247,85],{"emptyLinePlaceholder":84},[40,249,251,253,256,259,263,265,268,271],{"class":42,"line":250},29,[40,252,47],{"class":46},[40,254,255],{"class":50}," nginx",[40,257,258],{"class":54}," -t",[40,260,262],{"class":261},"sVt8B"," && ",[40,264,47],{"class":46},[40,266,267],{"class":50}," systemctl",[40,269,270],{"class":50}," reload",[40,272,273],{"class":50}," nginx\n",[14,275,276,277,279,280,283,284,287],{},"Use ",[26,278,28],{}," with a trailing slash, make sure the directories exist, and point Flask-generated URLs to ",[26,281,282],{},"\u002Fstatic\u002F..."," and ",[26,285,286],{},"\u002Fmedia\u002F...",".",[18,289,291],{"id":290},"whats-happening","What’s Happening",[14,293,294],{},"In production, Nginx should serve static files and uploaded media files directly instead of routing those requests through Gunicorn.",[296,297,298,302,305],"ul",{},[299,300,301],"li",{},"Static files are application assets such as CSS, JavaScript, fonts, and images bundled with the app.",[299,303,304],{},"Media files are user-generated uploads that must be stored in a writable directory and exposed through a dedicated URL path.",[299,306,307,308,310],{},"Broken static or media delivery usually comes from wrong paths, wrong Nginx ",[26,309,28],{}," usage, missing files, or permission problems.",[18,312,314],{"id":313},"step-by-step-guide","Step-by-Step Guide",[316,317,318,345,425,519,550,701,777,891,916,945],"ol",{},[299,319,320,324,327,328],{},[321,322,323],"strong",{},"Define stable filesystem paths for static and media files.",[325,326],"br",{},"Use directories outside rotating release paths.",[31,329,331],{"className":33,"code":330,"language":35,"meta":36,"style":36},"sudo mkdir -p \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\n",[26,332,333],{"__ignoreMap":36},[40,334,335,337,339,341,343],{"class":42,"line":43},[40,336,47],{"class":46},[40,338,51],{"class":50},[40,340,55],{"class":54},[40,342,58],{"class":50},[40,344,61],{"class":50},[299,346,347,350,352,353],{},[321,348,349],{},"Set ownership and permissions.",[325,351],{},"Nginx must be able to read both directories. Your app process must be able to write uploads to the media directory.",[31,354,356],{"className":33,"code":355,"language":35,"meta":36,"style":36},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyapp\nsudo find \u002Fvar\u002Fwww\u002Fmyapp -type d -exec chmod 755 {} \\;\nsudo find \u002Fvar\u002Fwww\u002Fmyapp -type f -exec chmod 644 {} \\;\n",[26,357,358,370,401],{"__ignoreMap":36},[40,359,360,362,364,366,368],{"class":42,"line":43},[40,361,47],{"class":46},[40,363,69],{"class":50},[40,365,72],{"class":54},[40,367,75],{"class":50},[40,369,78],{"class":50},[40,371,372,374,377,380,383,386,389,392,395,398],{"class":42,"line":64},[40,373,47],{"class":46},[40,375,376],{"class":50}," find",[40,378,379],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp",[40,381,382],{"class":54}," -type",[40,384,385],{"class":50}," d",[40,387,388],{"class":54}," -exec",[40,390,391],{"class":50}," chmod",[40,393,394],{"class":54}," 755",[40,396,397],{"class":50}," {}",[40,399,400],{"class":54}," \\;\n",[40,402,403,405,407,409,411,414,416,418,421,423],{"class":42,"line":81},[40,404,47],{"class":46},[40,406,376],{"class":50},[40,408,379],{"class":50},[40,410,382],{"class":54},[40,412,413],{"class":50}," f",[40,415,388],{"class":54},[40,417,391],{"class":50},[40,419,420],{"class":54}," 644",[40,422,397],{"class":50},[40,424,400],{"class":54},[299,426,427,430,432,433,514,516,517,287],{},[321,428,429],{},"Configure Flask path settings.",[325,431],{},"In Flask, keep the URL paths and disk paths explicit.",[31,434,438],{"className":435,"code":436,"language":437,"meta":36,"style":36},"language-python shiki shiki-themes github-light github-dark","import os\n\nSTATIC_URL = \"\u002Fstatic\"\nMEDIA_URL = \"\u002Fmedia\"\nSTATIC_ROOT = \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\"\nMEDIA_ROOT = \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\"\n\napp.config[\"UPLOAD_FOLDER\"] = MEDIA_ROOT\n","python",[26,439,440,448,452,463,473,483,493,497],{"__ignoreMap":36},[40,441,442,445],{"class":42,"line":43},[40,443,444],{"class":106},"import",[40,446,447],{"class":261}," os\n",[40,449,450],{"class":42,"line":64},[40,451,85],{"emptyLinePlaceholder":84},[40,453,454,457,460],{"class":42,"line":81},[40,455,456],{"class":54},"STATIC_URL",[40,458,459],{"class":106}," =",[40,461,462],{"class":50}," \"\u002Fstatic\"\n",[40,464,465,468,470],{"class":42,"line":88},[40,466,467],{"class":54},"MEDIA_URL",[40,469,459],{"class":106},[40,471,472],{"class":50}," \"\u002Fmedia\"\n",[40,474,475,478,480],{"class":42,"line":95},[40,476,477],{"class":54},"STATIC_ROOT",[40,479,459],{"class":106},[40,481,482],{"class":50}," \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\"\n",[40,484,485,488,490],{"class":42,"line":119},[40,486,487],{"class":54},"MEDIA_ROOT",[40,489,459],{"class":106},[40,491,492],{"class":50}," \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\"\n",[40,494,495],{"class":42,"line":125},[40,496,85],{"emptyLinePlaceholder":84},[40,498,499,502,505,508,511],{"class":42,"line":131},[40,500,501],{"class":261},"app.config[",[40,503,504],{"class":50},"\"UPLOAD_FOLDER\"",[40,506,507],{"class":261},"] ",[40,509,510],{"class":106},"=",[40,512,513],{"class":54}," MEDIA_ROOT\n",[325,515],{},"If you generate media URLs in templates or responses, make sure they resolve under ",[26,518,286],{},[299,520,521,524,526,527,530,531],{},[321,522,523],{},"Copy application static assets into the production static directory.",[325,525],{},"If your project stores assets in ",[26,528,529],{},"app\u002Fstatic\u002F",", sync them during deployment.",[31,532,534],{"className":33,"code":533,"language":35,"meta":36,"style":36},"rsync -av .\u002Fapp\u002Fstatic\u002F \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002F\n",[26,535,536],{"__ignoreMap":36},[40,537,538,541,544,547],{"class":42,"line":43},[40,539,540],{"class":46},"rsync",[40,542,543],{"class":54}," -av",[40,545,546],{"class":50}," .\u002Fapp\u002Fstatic\u002F",[40,548,549],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002F\n",[299,551,552,555,557,558],{},[321,553,554],{},"Save uploaded files into the media directory.",[325,556],{},"Example upload handling:",[31,559,561],{"className":435,"code":560,"language":437,"meta":36,"style":36},"import os\nfrom flask import request\nfrom werkzeug.utils import secure_filename\n\napp.config[\"UPLOAD_FOLDER\"] = \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\"\n\n@app.post(\"\u002Fupload\")\ndef upload_file():\n    file = request.files[\"file\"]\n    filename = secure_filename(file.filename)\n    file.save(os.path.join(app.config[\"UPLOAD_FOLDER\"], filename))\n    return {\"filename\": filename}, 201\n",[26,562,563,569,582,594,598,610,614,628,639,656,672,684],{"__ignoreMap":36},[40,564,565,567],{"class":42,"line":43},[40,566,444],{"class":106},[40,568,447],{"class":261},[40,570,571,574,577,579],{"class":42,"line":64},[40,572,573],{"class":106},"from",[40,575,576],{"class":261}," flask ",[40,578,444],{"class":106},[40,580,581],{"class":261}," request\n",[40,583,584,586,589,591],{"class":42,"line":81},[40,585,573],{"class":106},[40,587,588],{"class":261}," werkzeug.utils ",[40,590,444],{"class":106},[40,592,593],{"class":261}," secure_filename\n",[40,595,596],{"class":42,"line":88},[40,597,85],{"emptyLinePlaceholder":84},[40,599,600,602,604,606,608],{"class":42,"line":95},[40,601,501],{"class":261},[40,603,504],{"class":50},[40,605,507],{"class":261},[40,607,510],{"class":106},[40,609,492],{"class":50},[40,611,612],{"class":42,"line":119},[40,613,85],{"emptyLinePlaceholder":84},[40,615,616,619,622,625],{"class":42,"line":125},[40,617,618],{"class":46},"@app.post",[40,620,621],{"class":261},"(",[40,623,624],{"class":50},"\"\u002Fupload\"",[40,626,627],{"class":261},")\n",[40,629,630,633,636],{"class":42,"line":131},[40,631,632],{"class":106},"def",[40,634,635],{"class":46}," upload_file",[40,637,638],{"class":261},"():\n",[40,640,641,645,647,650,653],{"class":42,"line":137},[40,642,644],{"class":643},"s4XuR","    file",[40,646,459],{"class":106},[40,648,649],{"class":261}," request.files[",[40,651,652],{"class":50},"\"file\"",[40,654,655],{"class":261},"]\n",[40,657,658,661,663,666,669],{"class":42,"line":142},[40,659,660],{"class":261},"    filename ",[40,662,510],{"class":106},[40,664,665],{"class":261}," secure_filename(",[40,667,668],{"class":643},"file",[40,670,671],{"class":261},".filename)\n",[40,673,674,676,679,681],{"class":42,"line":148},[40,675,644],{"class":643},[40,677,678],{"class":261},".save(os.path.join(app.config[",[40,680,504],{"class":50},[40,682,683],{"class":261},"], filename))\n",[40,685,686,689,692,695,698],{"class":42,"line":154},[40,687,688],{"class":106},"    return",[40,690,691],{"class":261}," {",[40,693,694],{"class":50},"\"filename\"",[40,696,697],{"class":261},": filename}, ",[40,699,700],{"class":54},"201\n",[299,702,703,706,708,709,711,712,287,714,716,717],{},[321,704,705],{},"Update template and application URLs.",[325,707],{},"Static files should resolve under ",[26,710,282],{},". Uploaded files should resolve under ",[26,713,286],{},[325,715],{},"Example:",[31,718,722],{"className":719,"code":720,"language":721,"meta":36,"style":36},"language-html shiki shiki-themes github-light github-dark","\u003Clink rel=\"stylesheet\" href=\"\u002Fstatic\u002Fcss\u002Fapp.css\">\n\u003Cimg src=\"\u002Fmedia\u002Fexample.jpg\" alt=\"\">\n","html",[26,723,724,752],{"__ignoreMap":36},[40,725,726,729,733,736,738,741,744,746,749],{"class":42,"line":43},[40,727,728],{"class":261},"\u003C",[40,730,732],{"class":731},"s9eBZ","link",[40,734,735],{"class":46}," rel",[40,737,510],{"class":261},[40,739,740],{"class":50},"\"stylesheet\"",[40,742,743],{"class":46}," href",[40,745,510],{"class":261},[40,747,748],{"class":50},"\"\u002Fstatic\u002Fcss\u002Fapp.css\"",[40,750,751],{"class":261},">\n",[40,753,754,756,759,762,764,767,770,772,775],{"class":42,"line":64},[40,755,728],{"class":261},[40,757,758],{"class":731},"img",[40,760,761],{"class":46}," src",[40,763,510],{"class":261},[40,765,766],{"class":50},"\"\u002Fmedia\u002Fexample.jpg\"",[40,768,769],{"class":46}," alt",[40,771,510],{"class":261},[40,773,774],{"class":50},"\"\"",[40,776,751],{"class":261},[299,778,779,782,276,784,786,787,790,791],{},[321,780,781],{},"Configure Nginx to serve static and media files directly.",[325,783],{},[26,785,28],{},", not ",[26,788,789],{},"proxy_pass",", for these paths.",[31,792,796],{"className":793,"code":794,"language":795,"meta":36,"style":36},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002F;\n        access_log off;\n        expires 7d;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n        access_log off;\n        expires 1d;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n        include proxy_params;\n    }\n}\n","nginx",[26,797,798,802,806,810,814,818,822,826,830,835,839,843,847,851,855,859,863,867,871,875,879,883,887],{"__ignoreMap":36},[40,799,800],{"class":42,"line":43},[40,801,122],{},[40,803,804],{"class":42,"line":64},[40,805,128],{},[40,807,808],{"class":42,"line":81},[40,809,134],{},[40,811,812],{"class":42,"line":88},[40,813,85],{"emptyLinePlaceholder":84},[40,815,816],{"class":42,"line":95},[40,817,145],{},[40,819,820],{"class":42,"line":119},[40,821,151],{},[40,823,824],{"class":42,"line":125},[40,825,157],{},[40,827,828],{"class":42,"line":131},[40,829,163],{},[40,831,832],{"class":42,"line":137},[40,833,834],{},"        add_header Cache-Control \"public\";\n",[40,836,837],{"class":42,"line":142},[40,838,169],{},[40,840,841],{"class":42,"line":148},[40,842,85],{"emptyLinePlaceholder":84},[40,844,845],{"class":42,"line":154},[40,846,180],{},[40,848,849],{"class":42,"line":160},[40,850,186],{},[40,852,853],{"class":42,"line":166},[40,854,157],{},[40,856,857],{"class":42,"line":172},[40,858,197],{},[40,860,861],{"class":42,"line":177},[40,862,834],{},[40,864,865],{"class":42,"line":183},[40,866,169],{},[40,868,869],{"class":42,"line":189},[40,870,85],{"emptyLinePlaceholder":84},[40,872,873],{"class":42,"line":194},[40,874,213],{},[40,876,877],{"class":42,"line":200},[40,878,219],{},[40,880,881],{"class":42,"line":205},[40,882,225],{},[40,884,885],{"class":42,"line":210},[40,886,169],{},[40,888,889],{"class":42,"line":216},[40,890,236],{},[299,892,893,896],{},[321,894,895],{},"Enable the Nginx site if needed.",[31,897,899],{"className":33,"code":898,"language":35,"meta":36,"style":36},"sudo ln -s \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fmyapp\n",[26,900,901],{"__ignoreMap":36},[40,902,903,905,908,911,913],{"class":42,"line":43},[40,904,47],{"class":46},[40,906,907],{"class":50}," ln",[40,909,910],{"class":54}," -s",[40,912,103],{"class":50},[40,914,915],{"class":50}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fmyapp\n",[299,917,918,921],{},[321,919,920],{},"Validate and reload Nginx.",[31,922,924],{"className":33,"code":923,"language":35,"meta":36,"style":36},"sudo nginx -t\nsudo systemctl reload nginx\n",[26,925,926,935],{"__ignoreMap":36},[40,927,928,930,932],{"class":42,"line":43},[40,929,47],{"class":46},[40,931,255],{"class":50},[40,933,934],{"class":54}," -t\n",[40,936,937,939,941,943],{"class":42,"line":64},[40,938,47],{"class":46},[40,940,267],{"class":50},[40,942,270],{"class":50},[40,944,273],{"class":50},[299,946,947],{},[321,948,949],{},"Test static file serving directly.",[31,951,953],{"className":33,"code":952,"language":35,"meta":36,"style":36},"echo 'ok' | sudo tee \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002Fhealth.txt\ncurl -I http:\u002F\u002F127.0.0.1\u002Fstatic\u002Fhealth.txt\n",[26,954,955,974],{"__ignoreMap":36},[40,956,957,960,963,966,969,971],{"class":42,"line":43},[40,958,959],{"class":54},"echo",[40,961,962],{"class":50}," 'ok'",[40,964,965],{"class":106}," |",[40,967,968],{"class":46}," sudo",[40,970,100],{"class":50},[40,972,973],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002Fhealth.txt\n",[40,975,976,979,982],{"class":42,"line":64},[40,977,978],{"class":46},"curl",[40,980,981],{"class":54}," -I",[40,983,984],{"class":50}," http:\u002F\u002F127.0.0.1\u002Fstatic\u002Fhealth.txt\n",[14,986,987,988],{},"Expected result: ",[26,989,990],{},"HTTP\u002F1.1 200 OK",[316,992,993],{"start":148},[299,994,995],{},[321,996,997],{},"Test media file serving directly.",[31,999,1001],{"className":33,"code":1000,"language":35,"meta":36,"style":36},"echo 'upload-test' | sudo tee \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002Fsample.txt\ncurl -I http:\u002F\u002F127.0.0.1\u002Fmedia\u002Fsample.txt\n",[26,1002,1003,1019],{"__ignoreMap":36},[40,1004,1005,1007,1010,1012,1014,1016],{"class":42,"line":43},[40,1006,959],{"class":54},[40,1008,1009],{"class":50}," 'upload-test'",[40,1011,965],{"class":106},[40,1013,968],{"class":46},[40,1015,100],{"class":50},[40,1017,1018],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002Fsample.txt\n",[40,1020,1021,1023,1025],{"class":42,"line":64},[40,1022,978],{"class":46},[40,1024,981],{"class":54},[40,1026,1027],{"class":50}," http:\u002F\u002F127.0.0.1\u002Fmedia\u002Fsample.txt\n",[14,1029,987,1030],{},[26,1031,990],{},[316,1033,1034],{"start":154},[299,1035,1036],{},[321,1037,1038],{},"Check SELinux or service restrictions if applicable.",[14,1040,1041],{},"On hardened systems, Nginx may still fail even when Unix permissions look correct. Verify read access for Nginx and write access for the app service to the media directory.",[316,1043,1044],{"start":160},[299,1045,1046],{},[321,1047,1048],{},"Add static sync and media preservation to your deploy process.",[14,1050,1051],{},"Static files should be copied on every deploy. Media files should remain in persistent storage and not be replaced by a new release.",[316,1053,1054],{"start":166},[299,1055,1056],{},[321,1057,1058],{},"Use persistent storage for containers.",[14,1060,1061],{},"If you deploy with containers, mount a volume for uploads.",[31,1063,1067],{"className":1064,"code":1065,"language":1066,"meta":36,"style":36},"language-yaml shiki shiki-themes github-light github-dark","volumes:\n  - \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia\n","yaml",[26,1068,1069,1077],{"__ignoreMap":36},[40,1070,1071,1074],{"class":42,"line":43},[40,1072,1073],{"class":731},"volumes",[40,1075,1076],{"class":261},":\n",[40,1078,1079,1082],{"class":42,"line":64},[40,1080,1081],{"class":261},"  - ",[40,1083,1084],{"class":50},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia\n",[18,1086,1088],{"id":1087},"common-causes","Common Causes",[296,1090,1091,1114,1129,1135,1145,1151,1164,1177],{},[299,1092,1093,1096,1097,1100,1101,1103,1104,1107,1108,283,1111,287],{},[321,1094,1095],{},"Wrong Nginx directive"," → Using ",[26,1098,1099],{},"root"," instead of ",[26,1102,28],{}," for nested paths can map requests to the wrong directory → Use ",[26,1105,1106],{},"alias \u002Ffull\u002Fpath\u002F;"," inside ",[26,1109,1110],{},"location \u002Fstatic\u002F",[26,1112,1113],{},"location \u002Fmedia\u002F",[299,1115,1116,1119,1120,1122,1123,283,1126,1128],{},[321,1117,1118],{},"Missing trailing slash"," → ",[26,1121,28],{}," without the expected trailing slash can produce bad path resolution → Keep both the ",[26,1124,1125],{},"location",[26,1127,28],{}," forms consistent.",[299,1130,1131,1134],{},[321,1132,1133],{},"Files were never copied"," → Static assets exist in the repo but not in the production directory → Sync or collect them during deployment.",[299,1136,1137,1140,1141,1144],{},[321,1138,1139],{},"Uploads saved to the wrong directory"," → Flask writes files somewhere other than the Nginx-exposed media path → Set ",[26,1142,1143],{},"UPLOAD_FOLDER"," to the production media directory.",[299,1146,1147,1150],{},[321,1148,1149],{},"Permission denied"," → Nginx cannot read files or the app cannot write uploads → Fix ownership and mode bits for the service users.",[299,1152,1153,1156,1157,283,1160,1163],{},[321,1154,1155],{},"Requests hitting Gunicorn instead of Nginx static locations"," → Nginx location blocks are missing or shadowed by another rule → Put explicit ",[26,1158,1159],{},"\u002Fstatic\u002F",[26,1161,1162],{},"\u002Fmedia\u002F"," locations in the server block.",[299,1165,1166,1169,1170,1173,1174,1176],{},[321,1167,1168],{},"URL mismatch"," → App generates ",[26,1171,1172],{},"\u002Fuploads\u002F..."," while Nginx serves ",[26,1175,286],{}," → Make the Flask URL path and Nginx location path match exactly.",[299,1178,1179,1182,1183,1186],{},[321,1180,1181],{},"Ephemeral deploy path"," → Static or media files are stored inside a release directory that gets replaced → Move them to stable paths under ",[26,1184,1185],{},"\u002Fvar\u002Fwww"," or mounted storage.",[18,1188,1190],{"id":1189},"debugging-section","Debugging Section",[14,1192,1193],{},"Check configuration, files, permissions, and request routing.",[1195,1196,1198],"h3",{"id":1197},"nginx-config-validation","Nginx config validation",[31,1200,1202],{"className":33,"code":1201,"language":35,"meta":36,"style":36},"sudo nginx -t\nsudo nginx -T | less\n",[26,1203,1204,1212],{"__ignoreMap":36},[40,1205,1206,1208,1210],{"class":42,"line":43},[40,1207,47],{"class":46},[40,1209,255],{"class":50},[40,1211,934],{"class":54},[40,1213,1214,1216,1218,1221,1223],{"class":42,"line":64},[40,1215,47],{"class":46},[40,1217,255],{"class":50},[40,1219,1220],{"class":54}," -T",[40,1222,965],{"class":106},[40,1224,1225],{"class":46}," less\n",[14,1227,1228],{},"What to look for:",[296,1230,1231,1234,1237,1246],{},[299,1232,1233],{},"syntax errors",[299,1235,1236],{},"duplicate server blocks",[299,1238,1239,1240,1242,1243,1245],{},"missing ",[26,1241,1159],{}," or ",[26,1244,1162],{}," locations",[299,1247,1248,1249,1251],{},"wrong ",[26,1250,28],{}," paths",[1195,1253,1255],{"id":1254},"nginx-logs","Nginx logs",[31,1257,1259],{"className":33,"code":1258,"language":35,"meta":36,"style":36},"sudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\nsudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[26,1260,1261,1274],{"__ignoreMap":36},[40,1262,1263,1265,1268,1271],{"class":42,"line":43},[40,1264,47],{"class":46},[40,1266,1267],{"class":50}," tail",[40,1269,1270],{"class":54}," -f",[40,1272,1273],{"class":50}," \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[40,1275,1276,1278,1280,1282],{"class":42,"line":64},[40,1277,47],{"class":46},[40,1279,1267],{"class":50},[40,1281,1270],{"class":54},[40,1283,1284],{"class":50}," \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[14,1286,1228],{},[296,1288,1289,1295,1301],{},[299,1290,1291,1294],{},[26,1292,1293],{},"404"," for files that do not exist on disk",[299,1296,1297,1300],{},[26,1298,1299],{},"403"," caused by permissions",[299,1302,1303,1304,1242,1306,1308],{},"requests to ",[26,1305,1159],{},[26,1307,1162],{}," being handled unexpectedly",[1195,1310,1312],{"id":1311},"file-existence-and-path-traversal","File existence and path traversal",[31,1314,1316],{"className":33,"code":1315,"language":35,"meta":36,"style":36},"ls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\nls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\nnamei -l \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\u002Fhealth.txt\n",[26,1317,1318,1329,1337],{"__ignoreMap":36},[40,1319,1320,1323,1326],{"class":42,"line":43},[40,1321,1322],{"class":46},"ls",[40,1324,1325],{"class":54}," -lah",[40,1327,1328],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fstatic\n",[40,1330,1331,1333,1335],{"class":42,"line":64},[40,1332,1322],{"class":46},[40,1334,1325],{"class":54},[40,1336,61],{"class":50},[40,1338,1339,1342,1345],{"class":42,"line":81},[40,1340,1341],{"class":46},"namei",[40,1343,1344],{"class":54}," -l",[40,1346,973],{"class":50},[14,1348,1228],{},[296,1350,1351,1354,1357],{},[299,1352,1353],{},"missing files",[299,1355,1356],{},"unreadable parent directories",[299,1358,1359,1360,1363,1364,1366],{},"incorrect ownership on ",[26,1361,1362],{},"\u002Fvar",", ",[26,1365,1185],{},", or app subdirectories",[1195,1368,1370],{"id":1369},"local-request-tests","Local request tests",[31,1372,1374],{"className":33,"code":1373,"language":35,"meta":36,"style":36},"curl -I http:\u002F\u002F127.0.0.1\u002Fstatic\u002Fhealth.txt\ncurl -I http:\u002F\u002F127.0.0.1\u002Fmedia\u002Fsample.txt\n",[26,1375,1376,1384],{"__ignoreMap":36},[40,1377,1378,1380,1382],{"class":42,"line":43},[40,1379,978],{"class":46},[40,1381,981],{"class":54},[40,1383,984],{"class":50},[40,1385,1386,1388,1390],{"class":42,"line":64},[40,1387,978],{"class":46},[40,1389,981],{"class":54},[40,1391,1027],{"class":50},[14,1393,1228],{},[296,1395,1396,1402,1407],{},[299,1397,1398,1401],{},[26,1399,1400],{},"200 OK"," means Nginx can resolve and serve the file",[299,1403,1404,1406],{},[26,1405,1293],{}," means the URL-to-filesystem mapping is wrong or the file is missing",[299,1408,1409,1411],{},[26,1410,1299],{}," means permissions are blocking access",[1195,1413,1415],{"id":1414},"gunicorn-logs","Gunicorn logs",[31,1417,1419],{"className":33,"code":1418,"language":35,"meta":36,"style":36},"sudo journalctl -u gunicorn -n 100 --no-pager\n",[26,1420,1421],{"__ignoreMap":36},[40,1422,1423,1425,1428,1431,1434,1437,1440],{"class":42,"line":43},[40,1424,47],{"class":46},[40,1426,1427],{"class":50}," journalctl",[40,1429,1430],{"class":54}," -u",[40,1432,1433],{"class":50}," gunicorn",[40,1435,1436],{"class":54}," -n",[40,1438,1439],{"class":54}," 100",[40,1441,1442],{"class":54}," --no-pager\n",[14,1444,1228],{},[296,1446,1447,1450,1453],{},[299,1448,1449],{},"upload write failures",[299,1451,1452],{},"path misconfiguration in Flask",[299,1454,1455],{},"attempts to handle static URLs in the application instead of Nginx",[18,1457,1459],{"id":1458},"checklist","Checklist",[296,1461,1464,1479,1488,1494,1500,1506,1512,1521,1534,1544],{"className":1462},[1463],"contains-task-list",[299,1465,1468,1472,1473,1475,1476,1478],{"className":1466},[1467],"task-list-item",[1469,1470],"input",{"disabled":84,"type":1471},"checkbox"," ",[26,1474,1159],{}," is mapped in Nginx with ",[26,1477,28],{}," to the correct absolute directory.",[299,1480,1482,1472,1484,1475,1486,1478],{"className":1481},[1467],[1469,1483],{"disabled":84,"type":1471},[26,1485,1162],{},[26,1487,28],{},[299,1489,1491,1493],{"className":1490},[1467],[1469,1492],{"disabled":84,"type":1471}," Static files exist in the production static directory.",[299,1495,1497,1499],{"className":1496},[1467],[1469,1498],{"disabled":84,"type":1471}," Uploaded files are written to the production media directory.",[299,1501,1503,1505],{"className":1502},[1467],[1469,1504],{"disabled":84,"type":1471}," Nginx can read both static and media directories.",[299,1507,1509,1511],{"className":1508},[1467],[1469,1510],{"disabled":84,"type":1471}," The app process can write to the media directory.",[299,1513,1515,1472,1517,1520],{"className":1514},[1467],[1469,1516],{"disabled":84,"type":1471},[26,1518,1519],{},"nginx -t"," passes without errors.",[299,1522,1524,1526,1527,1530,1531,287],{"className":1523},[1467],[1469,1525],{"disabled":84,"type":1471}," A direct request to ",[26,1528,1529],{},"\u002Fstatic\u002Fhealth.txt"," returns ",[26,1532,1533],{},"200",[299,1535,1537,1539,1540,1530,1542,287],{"className":1536},[1467],[1469,1538],{"disabled":84,"type":1471}," A direct request to a sample file under ",[26,1541,1162],{},[26,1543,1533],{},[299,1545,1547,1549],{"className":1546},[1467],[1469,1548],{"disabled":84,"type":1471}," Static and media paths are preserved across deployments.",[18,1551,1553],{"id":1552},"related-guides","Related Guides",[296,1555,1556,1563,1569,1575],{},[299,1557,1558],{},[1559,1560,1562],"a",{"href":1561},"\u002Fdeploy\u002Fdeploy-flask-with-nginx-plus-gunicorn-step-by-step-guide","Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)",[299,1564,1565],{},[1559,1566,1568],{"href":1567},"\u002Ffix-issues\u002Fflask-static-files-not-loading-in-production","Flask Static Files Not Loading in Production",[299,1570,1571],{},[1559,1572,1574],{"href":1573},"\u002Ffix-issues\u002Fflask-media-files-not-serving-uploads-broken","Flask Media Files Not Serving (Uploads Broken)",[299,1576,1577],{},[1559,1578,1580],{"href":1579},"\u002Fchecklist\u002Fflask-production-checklist-everything-you-must-do","Flask Production Checklist (Everything You Must Do)",[18,1582,1584],{"id":1583},"faq","FAQ",[14,1586,1587,1590,1592],{},[321,1588,1589],{},"Q: Should Flask serve static files in production?",[325,1591],{},"\nA: No. Let Nginx serve static and media files directly for better performance and simpler routing.",[14,1594,1595,1598,1600],{},[321,1596,1597],{},"Q: What is the difference between static and media files?",[325,1599],{},"\nA: Static files are app assets shipped with the codebase. Media files are user uploads created after deployment.",[14,1602,1603,1606,1608],{},[321,1604,1605],{},"Q: Should media files live inside the app release directory?",[325,1607],{},"\nA: Usually no. Store them in a stable persistent path outside rotating release folders.",[14,1610,1611,1614,1616],{},[321,1612,1613],{},"Q: Why are my uploads saving but not loading?",[325,1615],{},"\nA: The app write path and Nginx read path are usually different, or Nginx lacks permission to read the files.",[14,1618,1619,1622,1624],{},[321,1620,1621],{},"Q: Should Nginx or Gunicorn serve static files?",[325,1623],{},"\nA: Nginx should serve static and media files directly in production.",[14,1626,1627,1630,1632,1633,1636],{},[321,1628,1629],{},"Q: What path should uploaded files use?",[325,1631],{},"\nA: Use a persistent writable directory such as ",[26,1634,1635],{},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia",", not a temporary release path.",[14,1638,1639,1642,1644],{},[321,1640,1641],{},"Q: Why do static files work locally but fail in production?",[325,1643],{},"\nA: Local Flask development serving can hide missing Nginx mappings, missing copied files, or production permission issues.",[14,1646,1647,1650,1652],{},[321,1648,1649],{},"Q: Should static and media use the same directory?",[325,1651],{},"\nA: No. Keep them separate so app assets and user uploads are managed independently.",[18,1654,1656],{"id":1655},"final-takeaway","Final Takeaway",[14,1658,1659,1660,283,1662,1664],{},"Production Flask setups should let Nginx serve ",[26,1661,1159],{},[26,1663,1162],{}," directly from stable filesystem paths. If the URL paths, disk paths, and permissions all match, static assets and uploads will load reliably.",[1666,1667,1668],"style",{},"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 pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":36,"searchDepth":64,"depth":64,"links":1670},[1671,1672,1673,1674,1675,1682,1683,1684,1685],{"id":20,"depth":64,"text":21},{"id":290,"depth":64,"text":291},{"id":313,"depth":64,"text":314},{"id":1087,"depth":64,"text":1088},{"id":1189,"depth":64,"text":1190,"children":1676},[1677,1678,1679,1680,1681],{"id":1197,"depth":81,"text":1198},{"id":1254,"depth":81,"text":1255},{"id":1311,"depth":81,"text":1312},{"id":1369,"depth":81,"text":1370},{"id":1414,"depth":81,"text":1415},{"id":1458,"depth":64,"text":1459},{"id":1552,"depth":64,"text":1553},{"id":1583,"depth":64,"text":1584},{"id":1655,"depth":64,"text":1656},"Complete guide on flask static and media files production setup for Flask production environments.","md",{"ogTitle":5,"ogDescription":1686,"twitterCard":1689,"robots":1690,"canonical":1691},"summary_large_image","index, follow","https:\u002F\u002Fflask-deployment.com\u002Fdeploy\u002Fflask-static-and-media-files-production-setup","\u002Fdeploy\u002Fflask-static-and-media-files-production-setup",{"title":5,"description":1686},"deploy\u002Fflask-static-and-media-files-production-setup","E-sV3MJqE1yKqQAVs0nB0tqry1YaCY2sgJgVs5n0uOs",1776805765728]