[{"data":1,"prerenderedAt":1455},["ShallowReactive",2],{"\u002Ffix-issues\u002Fflask-media-files-not-serving-uploads-broken":3},{"id":4,"title":5,"body":6,"description":1445,"extension":1446,"meta":1447,"navigation":95,"path":1451,"seo":1452,"stem":1453,"__hash__":1454},"content\u002Ffix-issues\u002Fflask-media-files-not-serving-uploads-broken.md","Flask Media Files Not Serving (Uploads Broken)",{"type":7,"value":8,"toc":1434},"minimark",[9,13,17,22,30,240,254,258,267,271,629,653,660,663,688,695,706,713,726,733,736,776,783,793,800,807,811,993,997,1000,1033,1036,1056,1059,1086,1089,1104,1107,1160,1163,1185,1188,1216,1219,1245,1249,1330,1334,1361,1365,1373,1389,1397,1405,1416,1420,1430],[10,11,5],"h1",{"id":12},"flask-media-files-not-serving-uploads-broken",[14,15,16],"p",{},"If uploaded files work in development but fail in production, this guide shows you how to restore media file delivery step-by-step. It covers the standard Flask + Gunicorn + Nginx setup, file path validation, URL mapping, and permission fixes.",[18,19,21],"h2",{"id":20},"quick-fix-quick-setup","Quick Fix \u002F Quick Setup",[14,23,24,25,29],{},"Create a dedicated media directory, set correct permissions, and map ",[26,27,28],"code",{},"\u002Fmedia\u002F"," to that directory in Nginx.",[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\u002Fmedia\nsudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\nsudo chmod -R 755 \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\n\n# Nginx: map URL path to media directory\nsudo tee \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp >\u002Fdev\u002Fnull \u003C\u003C'EOF'\nserver {\n    server_name example.com;\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n        autoindex off;\n    }\n\n    location \u002F {\n        include proxy_params;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n    }\n}\nEOF\n\nsudo nginx -t && sudo systemctl reload nginx\n","bash","",[26,38,39,59,75,90,97,104,128,134,140,145,151,157,163,169,174,180,186,192,197,203,209,214],{"__ignoreMap":36},[40,41,44,48,52,56],"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\u002Fmedia\n",[40,60,62,64,67,70,73],{"class":42,"line":61},2,[40,63,47],{"class":46},[40,65,66],{"class":50}," chown",[40,68,69],{"class":54}," -R",[40,71,72],{"class":50}," www-data:www-data",[40,74,58],{"class":50},[40,76,78,80,83,85,88],{"class":42,"line":77},3,[40,79,47],{"class":46},[40,81,82],{"class":50}," chmod",[40,84,69],{"class":54},[40,86,87],{"class":54}," 755",[40,89,58],{"class":50},[40,91,93],{"class":42,"line":92},4,[40,94,96],{"emptyLinePlaceholder":95},true,"\n",[40,98,100],{"class":42,"line":99},5,[40,101,103],{"class":102},"sJ8bj","# Nginx: map URL path to media directory\n",[40,105,107,109,112,115,119,122,125],{"class":42,"line":106},6,[40,108,47],{"class":46},[40,110,111],{"class":50}," tee",[40,113,114],{"class":50}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp",[40,116,118],{"class":117},"szBVR"," >",[40,120,121],{"class":50},"\u002Fdev\u002Fnull",[40,123,124],{"class":117}," \u003C\u003C",[40,126,127],{"class":50},"'EOF'\n",[40,129,131],{"class":42,"line":130},7,[40,132,133],{"class":50},"server {\n",[40,135,137],{"class":42,"line":136},8,[40,138,139],{"class":50},"    server_name example.com;\n",[40,141,143],{"class":42,"line":142},9,[40,144,96],{"emptyLinePlaceholder":95},[40,146,148],{"class":42,"line":147},10,[40,149,150],{"class":50},"    location \u002Fmedia\u002F {\n",[40,152,154],{"class":42,"line":153},11,[40,155,156],{"class":50},"        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n",[40,158,160],{"class":42,"line":159},12,[40,161,162],{"class":50},"        autoindex off;\n",[40,164,166],{"class":42,"line":165},13,[40,167,168],{"class":50},"    }\n",[40,170,172],{"class":42,"line":171},14,[40,173,96],{"emptyLinePlaceholder":95},[40,175,177],{"class":42,"line":176},15,[40,178,179],{"class":50},"    location \u002F {\n",[40,181,183],{"class":42,"line":182},16,[40,184,185],{"class":50},"        include proxy_params;\n",[40,187,189],{"class":42,"line":188},17,[40,190,191],{"class":50},"        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n",[40,193,195],{"class":42,"line":194},18,[40,196,168],{"class":50},[40,198,200],{"class":42,"line":199},19,[40,201,202],{"class":50},"}\n",[40,204,206],{"class":42,"line":205},20,[40,207,208],{"class":50},"EOF\n",[40,210,212],{"class":42,"line":211},21,[40,213,96],{"emptyLinePlaceholder":95},[40,215,217,219,222,225,229,231,234,237],{"class":42,"line":216},22,[40,218,47],{"class":46},[40,220,221],{"class":50}," nginx",[40,223,224],{"class":54}," -t",[40,226,228],{"class":227},"sVt8B"," && ",[40,230,47],{"class":46},[40,232,233],{"class":50}," systemctl",[40,235,236],{"class":50}," reload",[40,238,239],{"class":50}," nginx\n",[14,241,242,243,246,247,250,251,253],{},"Use ",[26,244,245],{},"alias",", not ",[26,248,249],{},"root",", for the ",[26,252,28],{}," location. The filesystem path must end with a trailing slash, and uploaded files must actually exist in that directory.",[18,255,257],{"id":256},"whats-happening","What’s Happening",[14,259,260,261,263,264,266],{},"In production, Gunicorn should not serve user-uploaded media files directly. Nginx should usually serve ",[26,262,28],{}," URLs from a directory on disk using an ",[26,265,245],{}," mapping. If the URL path, filesystem path, or permissions are wrong, uploads appear broken even if the app saved them correctly.",[18,268,270],{"id":269},"step-by-step-guide","Step-by-Step Guide",[272,273,274,310,352,372,395,424,486,523,557,624],"ol",{},[275,276,277,281,284,285,288,289,292,293],"li",{},[278,279,280],"strong",{},"Confirm the upload directory used by Flask",[282,283],"br",{},"Check your app config for ",[26,286,287],{},"MEDIA_ROOT",", ",[26,290,291],{},"UPLOAD_FOLDER",", or equivalent.",[31,294,298],{"className":295,"code":296,"language":297,"meta":36,"style":36},"language-python shiki shiki-themes github-light github-dark","UPLOAD_FOLDER = \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\"\n","python",[26,299,300],{"__ignoreMap":36},[40,301,302,304,307],{"class":42,"line":43},[40,303,291],{"class":54},[40,305,306],{"class":117}," =",[40,308,309],{"class":50}," \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\"\n",[275,311,312,315,317,318,320,321],{},[278,313,314],{},"Confirm the media URL prefix used by the app",[282,316],{},"Uploaded file links should resolve under a stable prefix such as ",[26,319,28],{},".",[31,322,324],{"className":295,"code":323,"language":297,"meta":36,"style":36},"image_url = f\"\u002Fmedia\u002F{filename}\"\n",[26,325,326],{"__ignoreMap":36},[40,327,328,331,334,337,340,343,346,349],{"class":42,"line":43},[40,329,330],{"class":227},"image_url ",[40,332,333],{"class":117},"=",[40,335,336],{"class":117}," f",[40,338,339],{"class":50},"\"\u002Fmedia\u002F",[40,341,342],{"class":54},"{",[40,344,345],{"class":227},"filename",[40,347,348],{"class":54},"}",[40,350,351],{"class":50},"\"\n",[275,353,354,357],{},[278,355,356],{},"Create the media directory if it does not exist",[31,358,360],{"className":33,"code":359,"language":35,"meta":36,"style":36},"sudo mkdir -p \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\n",[26,361,362],{"__ignoreMap":36},[40,363,364,366,368,370],{"class":42,"line":43},[40,365,47],{"class":46},[40,367,51],{"class":50},[40,369,55],{"class":54},[40,371,58],{"class":50},[275,373,374,377],{},[278,375,376],{},"Place a known file in the media directory for testing",[31,378,380],{"className":33,"code":379,"language":35,"meta":36,"style":36},"sudo cp test.jpg \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F\n",[26,381,382],{"__ignoreMap":36},[40,383,384,386,389,392],{"class":42,"line":43},[40,385,47],{"class":46},[40,387,388],{"class":50}," cp",[40,390,391],{"class":50}," test.jpg",[40,393,394],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F\n",[275,396,397,400,402,403,406,407],{},[278,398,399],{},"Set ownership so the app can write and Nginx can read",[282,401],{},"A common setup is to use ",[26,404,405],{},"www-data"," for both.",[31,408,410],{"className":33,"code":409,"language":35,"meta":36,"style":36},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\n",[26,411,412],{"__ignoreMap":36},[40,413,414,416,418,420,422],{"class":42,"line":43},[40,415,47],{"class":46},[40,417,66],{"class":50},[40,419,69],{"class":54},[40,421,72],{"class":50},[40,423,58],{"class":50},[275,425,426,429],{},[278,427,428],{},"Set directory and file permissions",[31,430,432],{"className":33,"code":431,"language":35,"meta":36,"style":36},"sudo find \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia -type d -exec chmod 755 {} \\;\nsudo find \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia -type f -exec chmod 644 {} \\;\n",[26,433,434,463],{"__ignoreMap":36},[40,435,436,438,441,444,447,450,453,455,457,460],{"class":42,"line":43},[40,437,47],{"class":46},[40,439,440],{"class":50}," find",[40,442,443],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia",[40,445,446],{"class":54}," -type",[40,448,449],{"class":50}," d",[40,451,452],{"class":54}," -exec",[40,454,82],{"class":50},[40,456,87],{"class":54},[40,458,459],{"class":50}," {}",[40,461,462],{"class":54}," \\;\n",[40,464,465,467,469,471,473,475,477,479,482,484],{"class":42,"line":61},[40,466,47],{"class":46},[40,468,440],{"class":50},[40,470,443],{"class":50},[40,472,446],{"class":54},[40,474,336],{"class":50},[40,476,452],{"class":54},[40,478,82],{"class":50},[40,480,481],{"class":54}," 644",[40,483,459],{"class":50},[40,485,462],{"class":54},[275,487,488,494,496,497],{},[278,489,490,491,493],{},"Configure Nginx to serve ",[26,492,28],{}," from disk",[282,495],{},"Add a dedicated location block.",[31,498,502],{"className":499,"code":500,"language":501,"meta":36,"style":36},"language-nginx shiki shiki-themes github-light github-dark","location \u002Fmedia\u002F {\n    alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n    autoindex off;\n}\n","nginx",[26,503,504,509,514,519],{"__ignoreMap":36},[40,505,506],{"class":42,"line":43},[40,507,508],{},"location \u002Fmedia\u002F {\n",[40,510,511],{"class":42,"line":61},[40,512,513],{},"    alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n",[40,515,516],{"class":42,"line":77},[40,517,518],{},"    autoindex off;\n",[40,520,521],{"class":42,"line":92},[40,522,202],{},[275,524,525,528,530,531,534,535,320,537,539,540],{},[278,526,527],{},"Make sure trailing slashes match",[282,529],{},"If the location ends with ",[26,532,533],{},"\u002F",", the alias path should also end with ",[26,536,533],{},[282,538],{},"Correct:",[31,541,543],{"className":499,"code":542,"language":501,"meta":36,"style":36},"location \u002Fmedia\u002F {\n    alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n}\n",[26,544,545,549,553],{"__ignoreMap":36},[40,546,547],{"class":42,"line":43},[40,548,508],{},[40,550,551],{"class":42,"line":61},[40,552,513],{},[40,554,555],{"class":42,"line":77},[40,556,202],{},[275,558,559,562],{},[278,560,561],{},"Use a full Nginx server block if needed",[31,563,565],{"className":499,"code":564,"language":501,"meta":36,"style":36},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002F;\n        autoindex off;\n    }\n\n    location \u002F {\n        include proxy_params;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n    }\n}\n",[26,566,567,571,576,580,584,588,592,596,600,604,608,612,616,620],{"__ignoreMap":36},[40,568,569],{"class":42,"line":43},[40,570,133],{},[40,572,573],{"class":42,"line":61},[40,574,575],{},"    listen 80;\n",[40,577,578],{"class":42,"line":77},[40,579,139],{},[40,581,582],{"class":42,"line":92},[40,583,96],{"emptyLinePlaceholder":95},[40,585,586],{"class":42,"line":99},[40,587,150],{},[40,589,590],{"class":42,"line":106},[40,591,156],{},[40,593,594],{"class":42,"line":130},[40,595,162],{},[40,597,598],{"class":42,"line":136},[40,599,168],{},[40,601,602],{"class":42,"line":142},[40,603,96],{"emptyLinePlaceholder":95},[40,605,606],{"class":42,"line":147},[40,607,179],{},[40,609,610],{"class":42,"line":153},[40,611,185],{},[40,613,614],{"class":42,"line":159},[40,615,191],{},[40,617,618],{"class":42,"line":165},[40,619,168],{},[40,621,622],{"class":42,"line":171},[40,623,202],{},[275,625,626],{},[278,627,628],{},"Test and reload Nginx",[31,630,632],{"className":33,"code":631,"language":35,"meta":36,"style":36},"sudo nginx -t\nsudo systemctl reload nginx\n",[26,633,634,643],{"__ignoreMap":36},[40,635,636,638,640],{"class":42,"line":43},[40,637,47],{"class":46},[40,639,221],{"class":50},[40,641,642],{"class":54}," -t\n",[40,644,645,647,649,651],{"class":42,"line":61},[40,646,47],{"class":46},[40,648,233],{"class":50},[40,650,236],{"class":50},[40,652,239],{"class":50},[272,654,655],{"start":153},[275,656,657],{},[278,658,659],{},"Request a known file directly",[14,661,662],{},"Test a file without going through app templates.",[31,664,666],{"className":33,"code":665,"language":35,"meta":36,"style":36},"curl -I http:\u002F\u002F127.0.0.1\u002Fmedia\u002Ftest.jpg\ncurl -I https:\u002F\u002Fexample.com\u002Fmedia\u002Ftest.jpg\n",[26,667,668,679],{"__ignoreMap":36},[40,669,670,673,676],{"class":42,"line":43},[40,671,672],{"class":46},"curl",[40,674,675],{"class":54}," -I",[40,677,678],{"class":50}," http:\u002F\u002F127.0.0.1\u002Fmedia\u002Ftest.jpg\n",[40,680,681,683,685],{"class":42,"line":61},[40,682,672],{"class":46},[40,684,675],{"class":54},[40,686,687],{"class":50}," https:\u002F\u002Fexample.com\u002Fmedia\u002Ftest.jpg\n",[272,689,690],{"start":159},[275,691,692],{},[278,693,694],{},"Verify the app and Nginx point to the same location",[14,696,697,698,701,702,705],{},"If Flask saves to ",[26,699,700],{},"\u002Fsrv\u002Fuploads"," but Nginx reads ",[26,703,704],{},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia",", media delivery will fail. Align both paths.",[272,707,708],{"start":165},[275,709,710],{},[278,711,712],{},"Check generated URLs",[14,714,715,716,719,720,723,724,320],{},"If your app generates absolute URLs, confirm the hostname and scheme are correct. Media links should not point to ",[26,717,718],{},"localhost",", an old domain, or an incorrect prefix like ",[26,721,722],{},"\u002Fuploads\u002F"," if Nginx serves ",[26,725,28],{},[272,727,728],{"start":171},[275,729,730],{},[278,731,732],{},"If using containers, verify persistent storage",[14,734,735],{},"The media directory must be mounted to persistent storage and visible to the process serving files.",[31,737,739],{"className":33,"code":738,"language":35,"meta":36,"style":36},"docker inspect \u003Ccontainer_name> | grep -A 20 Mounts\n",[26,740,741],{"__ignoreMap":36},[40,742,743,746,749,752,755,758,761,764,767,770,773],{"class":42,"line":43},[40,744,745],{"class":46},"docker",[40,747,748],{"class":50}," inspect",[40,750,751],{"class":117}," \u003C",[40,753,754],{"class":50},"container_nam",[40,756,757],{"class":227},"e",[40,759,760],{"class":117},">",[40,762,763],{"class":117}," |",[40,765,766],{"class":46}," grep",[40,768,769],{"class":54}," -A",[40,771,772],{"class":54}," 20",[40,774,775],{"class":50}," Mounts\n",[272,777,778],{"start":176},[275,779,780],{},[278,781,782],{},"Remove route conflicts",[14,784,785,786,789,790,792],{},"If Flask uses ",[26,787,788],{},"send_from_directory"," in production while Nginx is also serving ",[26,791,28],{},", make sure routes do not conflict. In standard production setups, let Nginx serve media files directly.",[272,794,795],{"start":182},[275,796,797],{},[278,798,799],{},"Retest with a new upload",[14,801,802,803,806],{},"Upload a new file, confirm it exists on disk, and request it using the generated ",[26,804,805],{},"\u002Fmedia\u002F..."," URL.",[18,808,810],{"id":809},"common-causes","Common Causes",[812,813,814,845,857,878,945,951,963,969,981,987],"ul",{},[275,815,816,819,820,822,823,825,826,828,829],{},[278,817,818],{},"Wrong Nginx directive"," → Using ",[26,821,249],{}," instead of ",[26,824,245],{}," for ",[26,827,28],{}," builds the wrong filesystem path → Replace it with:",[31,830,831],{"className":499,"code":542,"language":501,"meta":36,"style":36},[26,832,833,837,841],{"__ignoreMap":36},[40,834,835],{"class":42,"line":43},[40,836,508],{},[40,838,839],{"class":42,"line":61},[40,840,513],{},[40,842,843],{"class":42,"line":77},[40,844,202],{},[275,846,847,850,851,853,854,856],{},[278,848,849],{},"Wrong directory path"," → Flask saves uploads to one directory while Nginx reads another → Align ",[26,852,291],{}," and the Nginx ",[26,855,245],{}," target.",[275,858,859,862,863,866,867,870,871,873,874,877],{},[278,860,861],{},"Missing trailing slash"," → ",[26,864,865],{},"location \u002Fmedia\u002F"," with ",[26,868,869],{},"alias \u002Fpath\u002Fmedia"," can resolve paths incorrectly → Use ",[26,872,28],{}," and ",[26,875,876],{},"\u002Fpath\u002Fmedia\u002F"," together.",[275,879,880,883,884],{},[278,881,882],{},"Permissions problem"," → Uploads exist but Nginx cannot read them, or Flask cannot write them → Fix owner and mode:",[31,885,887],{"className":33,"code":886,"language":35,"meta":36,"style":36},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\nsudo find \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia -type d -exec chmod 755 {} \\;\nsudo find \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia -type f -exec chmod 644 {} \\;\n",[26,888,889,901,923],{"__ignoreMap":36},[40,890,891,893,895,897,899],{"class":42,"line":43},[40,892,47],{"class":46},[40,894,66],{"class":50},[40,896,69],{"class":54},[40,898,72],{"class":50},[40,900,58],{"class":50},[40,902,903,905,907,909,911,913,915,917,919,921],{"class":42,"line":61},[40,904,47],{"class":46},[40,906,440],{"class":50},[40,908,443],{"class":50},[40,910,446],{"class":54},[40,912,449],{"class":50},[40,914,452],{"class":54},[40,916,82],{"class":50},[40,918,87],{"class":54},[40,920,459],{"class":50},[40,922,462],{"class":54},[40,924,925,927,929,931,933,935,937,939,941,943],{"class":42,"line":77},[40,926,47],{"class":46},[40,928,440],{"class":50},[40,930,443],{"class":50},[40,932,446],{"class":54},[40,934,336],{"class":50},[40,936,452],{"class":54},[40,938,82],{"class":50},[40,940,481],{"class":54},[40,942,459],{"class":50},[40,944,462],{"class":54},[275,946,947,950],{},[278,948,949],{},"Uploads not persisted"," → Files disappear after restart or deploy, especially with Docker → Mount a persistent volume for media storage.",[275,952,953,956,957,959,960,962],{},[278,954,955],{},"Broken URL generation"," → Templates reference ",[26,958,722],{}," while Nginx serves ",[26,961,28],{}," → Standardize the URL prefix.",[275,964,965,968],{},[278,966,967],{},"Files never saved"," → The application failed to write uploads due to validation or write errors → Check Flask and Gunicorn logs.",[275,970,971,974,975,977,978,320],{},[278,972,973],{},"Location block conflict"," → Another Nginx rule overrides ",[26,976,28],{}," requests → Inspect the full loaded config with ",[26,979,980],{},"nginx -T",[275,982,983,986],{},[278,984,985],{},"SELinux or AppArmor restrictions"," → Access is denied despite normal Unix permissions → Adjust policy or move media to an allowed path.",[275,988,989,992],{},[278,990,991],{},"CDN or proxy cache issue"," → Old 404 responses are cached → Purge cache or test directly against origin.",[18,994,996],{"id":995},"debugging-section","Debugging Section",[14,998,999],{},"Check file existence first:",[31,1001,1003],{"className":33,"code":1002,"language":35,"meta":36,"style":36},"ls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\nstat \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002Ftest.jpg\nnamei -l \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002Ftest.jpg\n",[26,1004,1005,1015,1023],{"__ignoreMap":36},[40,1006,1007,1010,1013],{"class":42,"line":43},[40,1008,1009],{"class":46},"ls",[40,1011,1012],{"class":54}," -lah",[40,1014,58],{"class":50},[40,1016,1017,1020],{"class":42,"line":61},[40,1018,1019],{"class":54},"stat",[40,1021,1022],{"class":50}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fmedia\u002Ftest.jpg\n",[40,1024,1025,1028,1031],{"class":42,"line":77},[40,1026,1027],{"class":46},"namei",[40,1029,1030],{"class":54}," -l",[40,1032,1022],{"class":50},[14,1034,1035],{},"Test HTTP responses locally and publicly:",[31,1037,1038],{"className":33,"code":665,"language":35,"meta":36,"style":36},[26,1039,1040,1048],{"__ignoreMap":36},[40,1041,1042,1044,1046],{"class":42,"line":43},[40,1043,672],{"class":46},[40,1045,675],{"class":54},[40,1047,678],{"class":50},[40,1049,1050,1052,1054],{"class":42,"line":61},[40,1051,672],{"class":46},[40,1053,675],{"class":54},[40,1055,687],{"class":50},[14,1057,1058],{},"Validate the active Nginx configuration:",[31,1060,1062],{"className":33,"code":1061,"language":35,"meta":36,"style":36},"sudo nginx -t\nsudo nginx -T | less\n",[26,1063,1064,1072],{"__ignoreMap":36},[40,1065,1066,1068,1070],{"class":42,"line":43},[40,1067,47],{"class":46},[40,1069,221],{"class":50},[40,1071,642],{"class":54},[40,1073,1074,1076,1078,1081,1083],{"class":42,"line":61},[40,1075,47],{"class":46},[40,1077,221],{"class":50},[40,1079,1080],{"class":54}," -T",[40,1082,763],{"class":117},[40,1084,1085],{"class":46}," less\n",[14,1087,1088],{},"Reload after changes:",[31,1090,1092],{"className":33,"code":1091,"language":35,"meta":36,"style":36},"sudo systemctl reload nginx\n",[26,1093,1094],{"__ignoreMap":36},[40,1095,1096,1098,1100,1102],{"class":42,"line":43},[40,1097,47],{"class":46},[40,1099,233],{"class":50},[40,1101,236],{"class":50},[40,1103,239],{"class":50},[14,1105,1106],{},"Inspect Nginx logs:",[31,1108,1110],{"className":33,"code":1109,"language":35,"meta":36,"style":36},"sudo journalctl -u nginx -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[26,1111,1112,1133,1147],{"__ignoreMap":36},[40,1113,1114,1116,1119,1122,1124,1127,1130],{"class":42,"line":43},[40,1115,47],{"class":46},[40,1117,1118],{"class":50}," journalctl",[40,1120,1121],{"class":54}," -u",[40,1123,221],{"class":50},[40,1125,1126],{"class":54}," -n",[40,1128,1129],{"class":54}," 100",[40,1131,1132],{"class":54}," --no-pager\n",[40,1134,1135,1137,1140,1142,1144],{"class":42,"line":61},[40,1136,47],{"class":46},[40,1138,1139],{"class":50}," tail",[40,1141,1126],{"class":54},[40,1143,1129],{"class":54},[40,1145,1146],{"class":50}," \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[40,1148,1149,1151,1153,1155,1157],{"class":42,"line":77},[40,1150,47],{"class":46},[40,1152,1139],{"class":50},[40,1154,1126],{"class":54},[40,1156,1129],{"class":54},[40,1158,1159],{"class":50}," \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[14,1161,1162],{},"Inspect Gunicorn logs if uploads may not be saved correctly:",[31,1164,1166],{"className":33,"code":1165,"language":35,"meta":36,"style":36},"sudo journalctl -u gunicorn -n 100 --no-pager\n",[26,1167,1168],{"__ignoreMap":36},[40,1169,1170,1172,1174,1176,1179,1181,1183],{"class":42,"line":43},[40,1171,47],{"class":46},[40,1173,1118],{"class":50},[40,1175,1121],{"class":54},[40,1177,1178],{"class":50}," gunicorn",[40,1180,1126],{"class":54},[40,1182,1129],{"class":54},[40,1184,1132],{"class":54},[14,1186,1187],{},"If using Docker, verify mounts:",[31,1189,1190],{"className":33,"code":738,"language":35,"meta":36,"style":36},[26,1191,1192],{"__ignoreMap":36},[40,1193,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214],{"class":42,"line":43},[40,1195,745],{"class":46},[40,1197,748],{"class":50},[40,1199,751],{"class":117},[40,1201,754],{"class":50},[40,1203,757],{"class":227},[40,1205,760],{"class":117},[40,1207,763],{"class":117},[40,1209,766],{"class":46},[40,1211,769],{"class":54},[40,1213,772],{"class":54},[40,1215,775],{"class":50},[14,1217,1218],{},"What to look for:",[812,1220,1221,1227,1233,1239,1242],{},[275,1222,1223,1226],{},[26,1224,1225],{},"404"," in access logs → wrong path mapping or missing file",[275,1228,1229,1232],{},[26,1230,1231],{},"403"," → permissions or security policy issue",[275,1234,1235,1238],{},[26,1236,1237],{},"open() failed"," in Nginx logs → wrong alias path or unreadable file",[275,1240,1241],{},"file missing on disk → Flask upload handling failed or storage is ephemeral",[275,1243,1244],{},"requests hitting Flask instead of Nginx media location → route conflict or bad Nginx config",[18,1246,1248],{"id":1247},"checklist","Checklist",[812,1250,1253,1262,1268,1281,1287,1293,1303,1309,1318,1324],{"className":1251},[1252],"contains-task-list",[275,1254,1257,1261],{"className":1255},[1256],"task-list-item",[1258,1259],"input",{"disabled":95,"type":1260},"checkbox"," Uploaded files exist on disk in the expected media directory",[275,1263,1265,1267],{"className":1264},[1256],[1258,1266],{"disabled":95,"type":1260}," Flask saves uploads to the same directory Nginx serves",[275,1269,1271,1273,1274,1276,1277,246,1279],{"className":1270},[1256],[1258,1272],{"disabled":95,"type":1260}," Nginx has a ",[26,1275,865],{}," block with ",[26,1278,245],{},[26,1280,249],{},[275,1282,1284,1286],{"className":1283},[1256],[1258,1285],{"disabled":95,"type":1260}," The alias path ends with a trailing slash",[275,1288,1290,1292],{"className":1289},[1256],[1258,1291],{"disabled":95,"type":1260}," Directory ownership and permissions allow write by app and read by Nginx",[275,1294,1296,1298,1299,1302],{"className":1295},[1256],[1258,1297],{"disabled":95,"type":1260}," ",[26,1300,1301],{},"nginx -t"," passes without errors",[275,1304,1306,1308],{"className":1305},[1256],[1258,1307],{"disabled":95,"type":1260}," Reloading Nginx applies the config successfully",[275,1310,1312,1314,1315,1317],{"className":1311},[1256],[1258,1313],{"disabled":95,"type":1260}," A known file opens directly from its ",[26,1316,28],{}," URL",[275,1319,1321,1323],{"className":1320},[1256],[1258,1322],{"disabled":95,"type":1260}," New uploads remain available after app restart or deploy",[275,1325,1327,1329],{"className":1326},[1256],[1258,1328],{"disabled":95,"type":1260}," If using containers, media storage is persistent and shared correctly",[18,1331,1333],{"id":1332},"related-guides","Related Guides",[812,1335,1336,1343,1349,1355],{},[275,1337,1338],{},[1339,1340,1342],"a",{"href":1341},"\u002Fdeploy\u002Fdeploy-flask-with-nginx-plus-gunicorn-step-by-step-guide","Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)",[275,1344,1345],{},[1339,1346,1348],{"href":1347},"\u002Fdeploy\u002Fflask-static-and-media-files-production-setup","Flask Static and Media Files Production Setup",[275,1350,1351],{},[1339,1352,1354],{"href":1353},"\u002Ffix-issues\u002Fflask-404-on-static-or-media-files","Flask 404 on Static or Media Files",[275,1356,1357],{},[1339,1358,1360],{"href":1359},"\u002Fchecklist\u002Fflask-production-checklist-everything-you-must-do","Flask Production Checklist (Everything You Must Do)",[18,1362,1364],{"id":1363},"faq","FAQ",[14,1366,1367,1370,1372],{},[278,1368,1369],{},"Q: Should Gunicorn serve uploaded media files directly?",[282,1371],{},"\nA: No. In production, Nginx should normally serve uploaded files from disk.",[14,1374,1375,1381,1383,1384,1386,1387,320],{},[278,1376,1377,1378,1380],{},"Q: What Nginx directive should be used for ",[26,1379,28],{},"?",[282,1382],{},"\nA: Use ",[26,1385,245],{}," for a dedicated media URL prefix, not ",[26,1388,249],{},[14,1390,1391,1394,1396],{},[278,1392,1393],{},"Q: Why do uploads work locally but not on the server?",[282,1395],{},"\nA: The development server may serve files automatically, but production requires explicit Nginx media mapping and correct permissions.",[14,1398,1399,1402,1404],{},[278,1400,1401],{},"Q: Why are media files returning 404 after deploy?",[282,1403],{},"\nA: The files may be stored in the wrong directory, deleted during deploy, or served from a mismatched URL prefix.",[14,1406,1407,1410,1412,1413,1415],{},[278,1408,1409],{},"Q: How do I confirm the problem is Nginx and not Flask?",[282,1411],{},"\nA: Check whether the file exists on disk, then request it directly via the ",[26,1414,28],{}," URL and inspect Nginx logs.",[18,1417,1419],{"id":1418},"final-takeaway","Final Takeaway",[14,1421,1422,1423,1425,1426,1429],{},"Most broken Flask media delivery issues come from one of three problems: wrong path, wrong Nginx mapping, or wrong permissions. Validate where the app saves files, map that directory with an Nginx ",[26,1424,245],{},", and confirm the files exist and are readable. Once a direct ",[26,1427,1428],{},"\u002Fmedia\u002Ffile"," request works, upload handling in production is usually fixed.",[1431,1432,1433],"style",{},"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 .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);}",{"title":36,"searchDepth":61,"depth":61,"links":1435},[1436,1437,1438,1439,1440,1441,1442,1443,1444],{"id":20,"depth":61,"text":21},{"id":256,"depth":61,"text":257},{"id":269,"depth":61,"text":270},{"id":809,"depth":61,"text":810},{"id":995,"depth":61,"text":996},{"id":1247,"depth":61,"text":1248},{"id":1332,"depth":61,"text":1333},{"id":1363,"depth":61,"text":1364},{"id":1418,"depth":61,"text":1419},"Complete guide on flask media files not serving (uploads broken) for Flask production environments.","md",{"ogTitle":5,"ogDescription":1445,"twitterCard":1448,"robots":1449,"canonical":1450},"summary_large_image","index, follow","https:\u002F\u002Fflask-deployment.com\u002Ffix-issues\u002Fflask-media-files-not-serving-uploads-broken","\u002Ffix-issues\u002Fflask-media-files-not-serving-uploads-broken",{"title":5,"description":1445},"fix-issues\u002Fflask-media-files-not-serving-uploads-broken","qh3WjJ4GlSgvXv2rSOqgyIdMCeGmUmSa3YNuk1F4GUo",1776805765051]