[{"data":1,"prerenderedAt":89109},["ShallowReactive",2],{"layout-article-list":3,"article-deploy-connect-django-to-postgresql-production":87909},[4,1563,3106,4560,6340,8287,10248,11643,14033,16250,18012,20265,23047,24946,27137,28943,30690,32352,34169,35637,37883,39197,40229,42006,43561,44742,46541,48062,48833,49633,50905,51689,53474,55342,57199,58622,60127,61639,63440,65681,68056,69444,71141,72541,73731,75247,76590,78254,79875,82065,83423,85055,86647],{"id":5,"title":6,"body":7,"category":1541,"description":1542,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":1547,"navigation":309,"path":1548,"priority":1549,"related":1550,"role":1553,"section":1554,"seo":1555,"stack":1556,"stem":1559,"tags":1560,"type":1561,"__hash__":1562},"articles\u002Fbackup-and-restore-postgres-for-django.md","Backup and Restore PostgreSQL for Django Apps",{"type":8,"value":9,"toc":1488},"minimark",[10,15,24,27,31,37,40,44,48,51,59,62,78,85,89,95,99,102,105,186,193,222,228,235,242,250,253,270,274,277,281,284,392,396,399,497,503,516,520,523,556,559,583,586,612,616,626,630,633,637,660,664,667,693,699,728,731,735,752,755,759,762,766,819,822,826,891,895,965,971,974,978,981,1028,1031,1073,1076,1089,1095,1099,1102,1105,1130,1133,1147,1158,1162,1165,1169,1172,1190,1194,1197,1205,1208,1211,1214,1218,1221,1236,1242,1245,1256,1260,1263,1266,1282,1285,1311,1315,1318,1322,1328,1334,1338,1342,1348,1359,1362,1366,1369,1373,1376,1380,1383,1387,1390,1417,1421,1425,1431,1438,1443,1447,1450,1454,1457,1477,1481,1484],[11,12,14],"h2",{"id":13},"problem-statement","Problem statement",[16,17,18,19,23],"p",{},"A Django app is only recoverable if its PostgreSQL data is recoverable. In production, that means more than occasionally running ",[20,21,22],"code",{},"pg_dump",". You need a repeatable backup method, secure storage, a tested restore path, and a rollback plan for bad deploys, failed migrations, accidental deletes, or database host loss.",[16,25,26],{},"A common failure pattern is: the team has backups, but no one has verified that a restore works with the current Django app, migrations, extensions, and credentials. A backup that has never been restored is not a recovery plan.",[11,28,30],{"id":29},"quick-answer","Quick answer",[16,32,33,34,36],{},"For most Django apps, use ",[20,35,22],{}," to create consistent logical backups, store them securely off the database host, and regularly restore them into a separate database or staging environment to verify they work. Take a backup before risky deploys or migrations, and document the restore steps before you need them in an incident.",[16,38,39],{},"Also be clear about limits: a logical backup restores the database to the time the dump was taken. It does not preserve writes made after that point, so it is not a substitute for point-in-time recovery when low data loss is required.",[11,41,43],{"id":42},"step-by-step-solution","Step-by-step solution",[11,45,47],{"id":46},"_1-choose-the-right-backup-method","1. Choose the right backup method",[16,49,50],{},"For most small to medium Django deployments, logical backups are the practical default.",[52,53,55,56,58],"h3",{"id":54},"use-pg_dump-for-routine-django-backups","Use ",[20,57,22],{}," for routine Django backups",[16,60,61],{},"Logical backups are a good fit when you need:",[63,64,65,69,72,75],"ul",{},[66,67,68],"li",{},"pre-deploy backups",[66,70,71],{},"daily scheduled backups",[66,73,74],{},"portable dumps you can restore elsewhere",[66,76,77],{},"schema and data export for one database",[16,79,80,81,84],{},"Plain SQL dumps are easy to inspect. Custom-format dumps are usually better for production restores because ",[20,82,83],{},"pg_restore"," can restore them more flexibly.",[52,86,88],{"id":87},"know-when-logical-backups-are-not-enough","Know when logical backups are not enough",[16,90,91,92,94],{},"If you need point-in-time recovery, very low data-loss tolerance, or you run a large PostgreSQL database, you will usually need physical backups plus WAL archiving. That is a different setup than ",[20,93,22],{},". This page focuses on logical backup and restore, which is still the right baseline for many Django teams.",[11,96,98],{"id":97},"_2-collect-database-connection-details-safely","2. Collect database connection details safely",[16,100,101],{},"Do not hardcode credentials into commands or scripts committed to git.",[16,103,104],{},"A typical Django deployment exposes database settings through environment variables:",[106,107,112],"pre",{"className":108,"code":109,"language":110,"meta":111,"style":111},"language-bash shiki shiki-themes github-light github-dark","export DB_NAME=\"myapp\"\nexport DB_USER=\"myapp_user\"\nexport DB_PASSWORD=\"replace-me\"\nexport DB_HOST=\"127.0.0.1\"\nexport DB_PORT=\"5432\"\n","bash","",[20,113,114,134,147,160,173],{"__ignoreMap":111},[115,116,119,123,127,130],"span",{"class":117,"line":118},"line",1,[115,120,122],{"class":121},"szBVR","export",[115,124,126],{"class":125},"sVt8B"," DB_NAME",[115,128,129],{"class":121},"=",[115,131,133],{"class":132},"sZZnC","\"myapp\"\n",[115,135,137,139,142,144],{"class":117,"line":136},2,[115,138,122],{"class":121},[115,140,141],{"class":125}," DB_USER",[115,143,129],{"class":121},[115,145,146],{"class":132},"\"myapp_user\"\n",[115,148,150,152,155,157],{"class":117,"line":149},3,[115,151,122],{"class":121},[115,153,154],{"class":125}," DB_PASSWORD",[115,156,129],{"class":121},[115,158,159],{"class":132},"\"replace-me\"\n",[115,161,163,165,168,170],{"class":117,"line":162},4,[115,164,122],{"class":121},[115,166,167],{"class":125}," DB_HOST",[115,169,129],{"class":121},[115,171,172],{"class":132},"\"127.0.0.1\"\n",[115,174,176,178,181,183],{"class":117,"line":175},5,[115,177,122],{"class":121},[115,179,180],{"class":125}," DB_PORT",[115,182,129],{"class":121},[115,184,185],{"class":132},"\"5432\"\n",[16,187,188,189,192],{},"If you load values from a ",[20,190,191],{},".env"," file, do it in a controlled shell session on the server or backup runner:",[106,194,196],{"className":108,"code":195,"language":110,"meta":111,"style":111},"set -a\n. \u002Fsrv\u002Fmyapp\u002F.env\nset +a\n",[20,197,198,207,215],{"__ignoreMap":111},[115,199,200,204],{"class":117,"line":118},[115,201,203],{"class":202},"sj4cs","set",[115,205,206],{"class":202}," -a\n",[115,208,209,212],{"class":117,"line":136},[115,210,211],{"class":202},".",[115,213,214],{"class":132}," \u002Fsrv\u002Fmyapp\u002F.env\n",[115,216,217,219],{"class":117,"line":149},[115,218,203],{"class":202},[115,220,221],{"class":132}," +a\n",[16,223,224,225,227],{},"Only do this if the ",[20,226,191],{}," file is controlled, trusted, and not writable by other users on the system.",[16,229,230,231,234],{},"Be careful with shell history and CI logs. If possible, use a ",[20,232,233],{},".pgpass"," file instead of putting passwords inline.",[16,236,237,238,241],{},"Example format for ",[20,239,240],{},"~\u002F.pgpass",":",[106,243,248],{"className":244,"code":246,"language":247,"meta":111},[245],"language-text","127.0.0.1:5432:myapp:myapp_user:replace-me\n","text",[20,249,246],{"__ignoreMap":111},[16,251,252],{},"Restrict permissions:",[106,254,256],{"className":108,"code":255,"language":110,"meta":111,"style":111},"chmod 600 ~\u002F.pgpass\n",[20,257,258],{"__ignoreMap":111},[115,259,260,264,267],{"class":117,"line":118},[115,261,263],{"class":262},"sScJk","chmod",[115,265,266],{"class":202}," 600",[115,268,269],{"class":132}," ~\u002F.pgpass\n",[11,271,273],{"id":272},"_3-create-a-postgresql-backup-before-a-risky-django-deploy","3. Create a PostgreSQL backup before a risky Django deploy",[16,275,276],{},"Take a backup before schema migrations, data migrations, or major releases.",[52,278,280],{"id":279},"plain-sql-backup","Plain SQL backup",[16,282,283],{},"Use this when you want a simple, portable SQL file:",[106,285,287],{"className":108,"code":286,"language":110,"meta":111,"style":111},"backup_file=\"myapp-production-$(date +%Y%m%d-%H%M%S).sql\"\n\npg_dump \\\n  -h \"$DB_HOST\" \\\n  -p \"$DB_PORT\" \\\n  -U \"$DB_USER\" \\\n  -d \"$DB_NAME\" \\\n  > \"$backup_file\"\n",[20,288,289,305,311,318,334,348,363,378],{"__ignoreMap":111},[115,290,291,294,296,299,302],{"class":117,"line":118},[115,292,293],{"class":125},"backup_file",[115,295,129],{"class":121},[115,297,298],{"class":132},"\"myapp-production-$(",[115,300,301],{"class":262},"date",[115,303,304],{"class":132}," +%Y%m%d-%H%M%S).sql\"\n",[115,306,307],{"class":117,"line":136},[115,308,310],{"emptyLinePlaceholder":309},true,"\n",[115,312,313,315],{"class":117,"line":149},[115,314,22],{"class":262},[115,316,317],{"class":202}," \\\n",[115,319,320,323,326,329,332],{"class":117,"line":162},[115,321,322],{"class":202},"  -h",[115,324,325],{"class":132}," \"",[115,327,328],{"class":125},"$DB_HOST",[115,330,331],{"class":132},"\"",[115,333,317],{"class":202},[115,335,336,339,341,344,346],{"class":117,"line":175},[115,337,338],{"class":202},"  -p",[115,340,325],{"class":132},[115,342,343],{"class":125},"$DB_PORT",[115,345,331],{"class":132},[115,347,317],{"class":202},[115,349,351,354,356,359,361],{"class":117,"line":350},6,[115,352,353],{"class":202},"  -U",[115,355,325],{"class":132},[115,357,358],{"class":125},"$DB_USER",[115,360,331],{"class":132},[115,362,317],{"class":202},[115,364,366,369,371,374,376],{"class":117,"line":365},7,[115,367,368],{"class":202},"  -d",[115,370,325],{"class":132},[115,372,373],{"class":125},"$DB_NAME",[115,375,331],{"class":132},[115,377,317],{"class":202},[115,379,381,384,386,389],{"class":117,"line":380},8,[115,382,383],{"class":121},"  >",[115,385,325],{"class":132},[115,387,388],{"class":125},"$backup_file",[115,390,391],{"class":132},"\"\n",[52,393,395],{"id":394},"custom-format-backup","Custom-format backup",[16,397,398],{},"This is usually the better production choice:",[106,400,402],{"className":108,"code":401,"language":110,"meta":111,"style":111},"backup_file=\"myapp-production-$(date +%Y%m%d-%H%M%S).dump\"\n\npg_dump \\\n  -h \"$DB_HOST\" \\\n  -p \"$DB_PORT\" \\\n  -U \"$DB_USER\" \\\n  -d \"$DB_NAME\" \\\n  -F c \\\n  -f \"$backup_file\"\n",[20,403,404,417,421,427,439,451,463,475,485],{"__ignoreMap":111},[115,405,406,408,410,412,414],{"class":117,"line":118},[115,407,293],{"class":125},[115,409,129],{"class":121},[115,411,298],{"class":132},[115,413,301],{"class":262},[115,415,416],{"class":132}," +%Y%m%d-%H%M%S).dump\"\n",[115,418,419],{"class":117,"line":136},[115,420,310],{"emptyLinePlaceholder":309},[115,422,423,425],{"class":117,"line":149},[115,424,22],{"class":262},[115,426,317],{"class":202},[115,428,429,431,433,435,437],{"class":117,"line":162},[115,430,322],{"class":202},[115,432,325],{"class":132},[115,434,328],{"class":125},[115,436,331],{"class":132},[115,438,317],{"class":202},[115,440,441,443,445,447,449],{"class":117,"line":175},[115,442,338],{"class":202},[115,444,325],{"class":132},[115,446,343],{"class":125},[115,448,331],{"class":132},[115,450,317],{"class":202},[115,452,453,455,457,459,461],{"class":117,"line":350},[115,454,353],{"class":202},[115,456,325],{"class":132},[115,458,358],{"class":125},[115,460,331],{"class":132},[115,462,317],{"class":202},[115,464,465,467,469,471,473],{"class":117,"line":365},[115,466,368],{"class":202},[115,468,325],{"class":132},[115,470,373],{"class":125},[115,472,331],{"class":132},[115,474,317],{"class":202},[115,476,477,480,483],{"class":117,"line":380},[115,478,479],{"class":202},"  -F",[115,481,482],{"class":132}," c",[115,484,317],{"class":202},[115,486,488,491,493,495],{"class":117,"line":487},9,[115,489,490],{"class":202},"  -f",[115,492,325],{"class":132},[115,494,388],{"class":125},[115,496,391],{"class":132},[16,498,499,500,241],{},"Why use ",[20,501,502],{},"-F c",[63,504,505,510,513],{},[66,506,507,508],{},"works with ",[20,509,83],{},[66,511,512],{},"supports more selective restore operations",[66,514,515],{},"usually better for operational restore workflows",[52,517,519],{"id":518},"verify-the-backup-file-exists-and-is-readable","Verify the backup file exists and is readable",[16,521,522],{},"Immediately check that the dump was created and is not empty:",[106,524,526],{"className":108,"code":525,"language":110,"meta":111,"style":111},"ls -lh \"$backup_file\"\ntest -s \"$backup_file\"\n",[20,527,528,542],{"__ignoreMap":111},[115,529,530,533,536,538,540],{"class":117,"line":118},[115,531,532],{"class":262},"ls",[115,534,535],{"class":202}," -lh",[115,537,325],{"class":132},[115,539,388],{"class":125},[115,541,391],{"class":132},[115,543,544,547,550,552,554],{"class":117,"line":136},[115,545,546],{"class":202},"test",[115,548,549],{"class":202}," -s",[115,551,325],{"class":132},[115,553,388],{"class":125},[115,555,391],{"class":132},[16,557,558],{},"For custom dumps, also confirm PostgreSQL can read the archive:",[106,560,562],{"className":108,"code":561,"language":110,"meta":111,"style":111},"pg_restore --list \"$backup_file\" | head\n",[20,563,564],{"__ignoreMap":111},[115,565,566,568,571,573,575,577,580],{"class":117,"line":118},[115,567,83],{"class":262},[115,569,570],{"class":202}," --list",[115,572,325],{"class":132},[115,574,388],{"class":125},[115,576,331],{"class":132},[115,578,579],{"class":121}," |",[115,581,582],{"class":262}," head\n",[16,584,585],{},"If you want an integrity record, create a checksum tied to the same filename:",[106,587,589],{"className":108,"code":588,"language":110,"meta":111,"style":111},"sha256sum \"$backup_file\" > \"$backup_file.sha256\"\n",[20,590,591],{"__ignoreMap":111},[115,592,593,596,598,600,602,605,607,609],{"class":117,"line":118},[115,594,595],{"class":262},"sha256sum",[115,597,325],{"class":132},[115,599,388],{"class":125},[115,601,331],{"class":132},[115,603,604],{"class":121}," >",[115,606,325],{"class":132},[115,608,388],{"class":125},[115,610,611],{"class":132},".sha256\"\n",[52,613,615],{"id":614},"include-globals-only-when-needed","Include globals only when needed",[16,617,618,619,622,623,625],{},"Most Django app backups should focus on the application database, not the whole PostgreSQL cluster. Roles and global objects can be exported separately with ",[20,620,621],{},"pg_dumpall --globals-only",". This is necessary only if your recovery plan depends on recreating roles, grants, or other cluster-level objects outside managed provisioning, because ",[20,624,22],{}," of a single database does not include them.",[11,627,629],{"id":628},"_4-protect-and-store-backups-off-host","4. Protect and store backups off-host",[16,631,632],{},"A dump file on the same disk as the database is not enough.",[52,634,636],{"id":635},"restrict-local-permissions","Restrict local permissions",[106,638,640],{"className":108,"code":639,"language":110,"meta":111,"style":111},"chmod 600 \"$backup_file\" \"$backup_file.sha256\"\n",[20,641,642],{"__ignoreMap":111},[115,643,644,646,648,650,652,654,656,658],{"class":117,"line":118},[115,645,263],{"class":262},[115,647,266],{"class":202},[115,649,325],{"class":132},[115,651,388],{"class":125},[115,653,331],{"class":132},[115,655,325],{"class":132},[115,657,388],{"class":125},[115,659,611],{"class":132},[52,661,663],{"id":662},"copy-the-backup-to-another-system","Copy the backup to another system",[16,665,666],{},"Use SSH-based transfer to a separate backup host:",[106,668,670],{"className":108,"code":669,"language":110,"meta":111,"style":111},"scp \"$backup_file\" \"$backup_file.sha256\" backup@example-backup-host:\u002Fbackups\u002Fmyapp\u002F\n",[20,671,672],{"__ignoreMap":111},[115,673,674,677,679,681,683,685,687,690],{"class":117,"line":118},[115,675,676],{"class":262},"scp",[115,678,325],{"class":132},[115,680,388],{"class":125},[115,682,331],{"class":132},[115,684,325],{"class":132},[115,686,388],{"class":125},[115,688,689],{"class":132},".sha256\"",[115,691,692],{"class":132}," backup@example-backup-host:\u002Fbackups\u002Fmyapp\u002F\n",[16,694,695,696,241],{},"Or with ",[20,697,698],{},"rsync",[106,700,702],{"className":108,"code":701,"language":110,"meta":111,"style":111},"rsync -av --progress \"$backup_file\" \"$backup_file.sha256\" backup@example-backup-host:\u002Fbackups\u002Fmyapp\u002F\n",[20,703,704],{"__ignoreMap":111},[115,705,706,708,711,714,716,718,720,722,724,726],{"class":117,"line":118},[115,707,698],{"class":262},[115,709,710],{"class":202}," -av",[115,712,713],{"class":202}," --progress",[115,715,325],{"class":132},[115,717,388],{"class":125},[115,719,331],{"class":132},[115,721,325],{"class":132},[115,723,388],{"class":125},[115,725,689],{"class":132},[115,727,692],{"class":132},[16,729,730],{},"Verification matters here too. Confirm the file arrived and has the expected size.",[52,732,734],{"id":733},"basic-backup-security-rules","Basic backup security rules",[63,736,737,740,743,746,749],{},[66,738,739],{},"do not leave dump files world-readable",[66,741,742],{},"do not rely only on local storage on the database host",[66,744,745],{},"use encrypted transport for remote copy",[66,747,748],{},"encrypt backup storage at rest, especially on backup hosts or object storage",[66,750,751],{},"apply a retention policy so disks do not fill up",[16,753,754],{},"If you use object storage, prefer encrypted buckets and restricted access policies.",[11,756,758],{"id":757},"_5-restore-safely-into-a-new-database-first","5. Restore safely into a new database first",[16,760,761],{},"Never test a restore by writing directly into production.",[52,763,765],{"id":764},"create-a-fresh-restore-target","Create a fresh restore target",[106,767,769],{"className":108,"code":768,"language":110,"meta":111,"style":111},"createdb \\\n  -h \"$DB_HOST\" \\\n  -p \"$DB_PORT\" \\\n  -U \"$DB_USER\" \\\n  restored_myapp\n",[20,770,771,778,790,802,814],{"__ignoreMap":111},[115,772,773,776],{"class":117,"line":118},[115,774,775],{"class":262},"createdb",[115,777,317],{"class":202},[115,779,780,782,784,786,788],{"class":117,"line":136},[115,781,322],{"class":202},[115,783,325],{"class":132},[115,785,328],{"class":125},[115,787,331],{"class":132},[115,789,317],{"class":202},[115,791,792,794,796,798,800],{"class":117,"line":149},[115,793,338],{"class":202},[115,795,325],{"class":132},[115,797,343],{"class":125},[115,799,331],{"class":132},[115,801,317],{"class":202},[115,803,804,806,808,810,812],{"class":117,"line":162},[115,805,353],{"class":202},[115,807,325],{"class":132},[115,809,358],{"class":125},[115,811,331],{"class":132},[115,813,317],{"class":202},[115,815,816],{"class":117,"line":175},[115,817,818],{"class":132},"  restored_myapp\n",[16,820,821],{},"If the role does not have permission to create databases, use an admin role for this step.",[52,823,825],{"id":824},"restore-a-plain-sql-dump","Restore a plain SQL dump",[106,827,829],{"className":108,"code":828,"language":110,"meta":111,"style":111},"psql \\\n  -h \"$DB_HOST\" \\\n  -p \"$DB_PORT\" \\\n  -U \"$DB_USER\" \\\n  -d restored_myapp \\\n  \u003C myapp-production-20260424-120000.sql\n",[20,830,831,838,850,862,874,883],{"__ignoreMap":111},[115,832,833,836],{"class":117,"line":118},[115,834,835],{"class":262},"psql",[115,837,317],{"class":202},[115,839,840,842,844,846,848],{"class":117,"line":136},[115,841,322],{"class":202},[115,843,325],{"class":132},[115,845,328],{"class":125},[115,847,331],{"class":132},[115,849,317],{"class":202},[115,851,852,854,856,858,860],{"class":117,"line":149},[115,853,338],{"class":202},[115,855,325],{"class":132},[115,857,343],{"class":125},[115,859,331],{"class":132},[115,861,317],{"class":202},[115,863,864,866,868,870,872],{"class":117,"line":162},[115,865,353],{"class":202},[115,867,325],{"class":132},[115,869,358],{"class":125},[115,871,331],{"class":132},[115,873,317],{"class":202},[115,875,876,878,881],{"class":117,"line":175},[115,877,368],{"class":202},[115,879,880],{"class":132}," restored_myapp",[115,882,317],{"class":202},[115,884,885,888],{"class":117,"line":350},[115,886,887],{"class":121},"  \u003C",[115,889,890],{"class":132}," myapp-production-20260424-120000.sql\n",[52,892,894],{"id":893},"restore-a-custom-dump","Restore a custom dump",[106,896,898],{"className":108,"code":897,"language":110,"meta":111,"style":111},"pg_restore \\\n  -h \"$DB_HOST\" \\\n  -p \"$DB_PORT\" \\\n  -U \"$DB_USER\" \\\n  -d restored_myapp \\\n  --clean --if-exists \\\n  myapp-production-20260424-120000.dump\n",[20,899,900,906,918,930,942,950,960],{"__ignoreMap":111},[115,901,902,904],{"class":117,"line":118},[115,903,83],{"class":262},[115,905,317],{"class":202},[115,907,908,910,912,914,916],{"class":117,"line":136},[115,909,322],{"class":202},[115,911,325],{"class":132},[115,913,328],{"class":125},[115,915,331],{"class":132},[115,917,317],{"class":202},[115,919,920,922,924,926,928],{"class":117,"line":149},[115,921,338],{"class":202},[115,923,325],{"class":132},[115,925,343],{"class":125},[115,927,331],{"class":132},[115,929,317],{"class":202},[115,931,932,934,936,938,940],{"class":117,"line":162},[115,933,353],{"class":202},[115,935,325],{"class":132},[115,937,358],{"class":125},[115,939,331],{"class":132},[115,941,317],{"class":202},[115,943,944,946,948],{"class":117,"line":175},[115,945,368],{"class":202},[115,947,880],{"class":132},[115,949,317],{"class":202},[115,951,952,955,958],{"class":117,"line":350},[115,953,954],{"class":202},"  --clean",[115,956,957],{"class":202}," --if-exists",[115,959,317],{"class":202},[115,961,962],{"class":117,"line":365},[115,963,964],{"class":132},"  myapp-production-20260424-120000.dump\n",[16,966,967,970],{},[20,968,969],{},"--clean --if-exists"," is useful when the target already contains objects and you intend a full replacement. Use it carefully. For a brand new empty database, it is usually harmless but not strictly required.",[16,972,973],{},"This restores database objects and data into the target database. It does not recreate cluster-wide roles, grants, or other global objects unless you back those up separately.",[52,975,977],{"id":976},"verification-checks-after-restore","Verification checks after restore",[16,979,980],{},"Confirm tables exist:",[106,982,984],{"className":108,"code":983,"language":110,"meta":111,"style":111},"psql -h \"$DB_HOST\" -p \"$DB_PORT\" -U \"$DB_USER\" -d restored_myapp -c \"\\dt\"\n",[20,985,986],{"__ignoreMap":111},[115,987,988,990,993,995,997,999,1002,1004,1006,1008,1011,1013,1015,1017,1020,1022,1025],{"class":117,"line":118},[115,989,835],{"class":262},[115,991,992],{"class":202}," -h",[115,994,325],{"class":132},[115,996,328],{"class":125},[115,998,331],{"class":132},[115,1000,1001],{"class":202}," -p",[115,1003,325],{"class":132},[115,1005,343],{"class":125},[115,1007,331],{"class":132},[115,1009,1010],{"class":202}," -U",[115,1012,325],{"class":132},[115,1014,358],{"class":125},[115,1016,331],{"class":132},[115,1018,1019],{"class":202}," -d",[115,1021,880],{"class":132},[115,1023,1024],{"class":202}," -c",[115,1026,1027],{"class":132}," \"\\dt\"\n",[16,1029,1030],{},"Check migration history:",[106,1032,1034],{"className":108,"code":1033,"language":110,"meta":111,"style":111},"psql -h \"$DB_HOST\" -p \"$DB_PORT\" -U \"$DB_USER\" -d restored_myapp -c \"SELECT app, name FROM django_migrations ORDER BY app, name;\"\n",[20,1035,1036],{"__ignoreMap":111},[115,1037,1038,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060,1062,1064,1066,1068,1070],{"class":117,"line":118},[115,1039,835],{"class":262},[115,1041,992],{"class":202},[115,1043,325],{"class":132},[115,1045,328],{"class":125},[115,1047,331],{"class":132},[115,1049,1001],{"class":202},[115,1051,325],{"class":132},[115,1053,343],{"class":125},[115,1055,331],{"class":132},[115,1057,1010],{"class":202},[115,1059,325],{"class":132},[115,1061,358],{"class":125},[115,1063,331],{"class":132},[115,1065,1019],{"class":202},[115,1067,880],{"class":132},[115,1069,1024],{"class":202},[115,1071,1072],{"class":132}," \"SELECT app, name FROM django_migrations ORDER BY app, name;\"\n",[16,1074,1075],{},"Check that the restore command exited successfully:",[106,1077,1079],{"className":108,"code":1078,"language":110,"meta":111,"style":111},"echo $?\n",[20,1080,1081],{"__ignoreMap":111},[115,1082,1083,1086],{"class":117,"line":118},[115,1084,1085],{"class":202},"echo",[115,1087,1088],{"class":202}," $?\n",[16,1090,1091,1092,211],{},"A successful restore should return ",[20,1093,1094],{},"0",[11,1096,1098],{"id":1097},"_6-validate-the-restored-database-with-django","6. Validate the restored database with Django",[16,1100,1101],{},"A PostgreSQL restore is not complete until the Django app can use it.",[16,1103,1104],{},"Point your Django app or staging environment at the restored database, then run:",[106,1106,1108],{"className":108,"code":1107,"language":110,"meta":111,"style":111},"python manage.py check\npython manage.py showmigrations\n",[20,1109,1110,1121],{"__ignoreMap":111},[115,1111,1112,1115,1118],{"class":117,"line":118},[115,1113,1114],{"class":262},"python",[115,1116,1117],{"class":132}," manage.py",[115,1119,1120],{"class":132}," check\n",[115,1122,1123,1125,1127],{"class":117,"line":136},[115,1124,1114],{"class":262},[115,1126,1117],{"class":132},[115,1128,1129],{"class":132}," showmigrations\n",[16,1131,1132],{},"Also verify:",[63,1134,1135,1138,1141,1144],{},[66,1136,1137],{},"admin login works",[66,1139,1140],{},"expected users and recent records exist",[66,1142,1143],{},"background job dependencies are present",[66,1145,1146],{},"critical read and write paths behave correctly",[16,1148,1149,1150,1153,1154,1157],{},"If your app depends on PostgreSQL extensions such as ",[20,1151,1152],{},"pgcrypto",", ",[20,1155,1156],{},"uuid-ossp",", or full-text search features, verify those exist in the restored database too.",[11,1159,1161],{"id":1160},"_7-plan-rollback-and-incident-recovery","7. Plan rollback and incident recovery",[16,1163,1164],{},"Backups are most useful when tied to a deployment process.",[52,1166,1168],{"id":1167},"before-migrations-or-risky-releases","Before migrations or risky releases",[16,1170,1171],{},"Document a pre-deploy sequence:",[1173,1174,1175,1178,1181,1184,1187],"ol",{},[66,1176,1177],{},"take backup",[66,1179,1180],{},"verify backup file exists and is readable",[66,1182,1183],{},"deploy code",[66,1185,1186],{},"run migrations",[66,1188,1189],{},"run smoke checks",[52,1191,1193],{"id":1192},"if-the-deploy-fails","If the deploy fails",[16,1195,1196],{},"Rollback often requires both:",[63,1198,1199,1202],{},[66,1200,1201],{},"restoring the previous application version",[66,1203,1204],{},"restoring the database to a compatible state",[16,1206,1207],{},"Be careful with backward-incompatible migrations. If a migration drops columns or rewrites data, restoring only the app code may not be enough. You may need a full database restore from the pre-deploy backup.",[16,1209,1210],{},"A logical backup restores the database to the time the dump was taken. It does not preserve writes made after that point, so it is not a substitute for point-in-time recovery when low data loss is required.",[16,1212,1213],{},"For high-risk restores or cutovers, consider quiescing writes or putting the app into maintenance mode so you do not create new data while switching database state.",[52,1215,1217],{"id":1216},"define-rpo-and-rto","Define RPO and RTO",[16,1219,1220],{},"For operations planning, decide:",[63,1222,1223,1230],{},[66,1224,1225,1229],{},[1226,1227,1228],"strong",{},"RPO",": how much data loss is acceptable",[66,1231,1232,1235],{},[1226,1233,1234],{},"RTO",": how quickly the service must recover",[16,1237,1238,1239,1241],{},"Those targets determine whether daily ",[20,1240,22],{}," is enough or whether you need continuous archiving and point-in-time recovery.",[16,1243,1244],{},"A practical baseline for many small teams is:",[63,1246,1247,1250,1253],{},[66,1248,1249],{},"scheduled daily backups at minimum",[66,1251,1252],{},"an extra pre-deploy backup before risky schema or data changes",[66,1254,1255],{},"regular restore drills, such as monthly or after major database changes",[11,1257,1259],{"id":1258},"_8-automate-recurring-backups","8. Automate recurring backups",[16,1261,1262],{},"For a production Django app, recurring backups should not depend on manual shell access.",[16,1264,1265],{},"Use:",[63,1267,1268,1273,1279],{},[66,1269,1270],{},[20,1271,1272],{},"cron",[66,1274,1275,1278],{},[20,1276,1277],{},"systemd"," timers",[66,1280,1281],{},"a CI job running in a trusted environment",[16,1283,1284],{},"A simple backup script should:",[63,1286,1287,1290,1293,1296,1299,1302,1305,1308],{},[66,1288,1289],{},"load environment variables",[66,1291,1292],{},"create a timestamped dump",[66,1294,1295],{},"verify file creation",[66,1297,1298],{},"verify the archive is readable",[66,1300,1301],{},"generate a checksum",[66,1303,1304],{},"upload off-host",[66,1306,1307],{},"prune old backups",[66,1309,1310],{},"exit non-zero on failure",[52,1312,1314],{"id":1313},"when-to-convert-this-into-a-reusable-script","When to convert this into a reusable script",[16,1316,1317],{},"If you are taking pre-deploy backups repeatedly, copying dumps off-host manually, or testing restores with the same sequence each time, convert the process into a script or template. The first automation targets should usually be timestamped backup creation, remote upload, retention cleanup, and restore verification into a temporary database.",[11,1319,1321],{"id":1320},"explanation","Explanation",[16,1323,1324,1325,1327],{},"This setup works because it covers the full recovery chain, not just dump creation. ",[20,1326,22],{}," gives you a consistent logical backup of the Django application database. Off-host storage protects against server loss. Restoring into a separate database verifies that the dump is usable without risking live data. Running Django checks after restore confirms that the application, not just PostgreSQL, can operate on the recovered dataset.",[16,1329,1330,1331,1333],{},"Use plain SQL dumps when portability and inspection matter most. Use custom-format dumps when you want a cleaner operational restore path with ",[20,1332,83],{},". If your database is large or your recovery requirements are strict, move beyond logical backups to physical backups and WAL archiving for point-in-time recovery.",[11,1335,1337],{"id":1336},"edge-cases-notes","Edge cases \u002F notes",[52,1339,1341],{"id":1340},"docker-based-django-deployments","Docker-based Django deployments",[16,1343,1344,1345,1347],{},"If PostgreSQL runs in Docker, you can run ",[20,1346,22],{}," from:",[63,1349,1350,1353,1356],{},[66,1351,1352],{},"the database container",[66,1354,1355],{},"a separate client container on the same network",[66,1357,1358],{},"the app container, if PostgreSQL client tools are installed",[16,1360,1361],{},"Do not assume container-local storage is durable backup storage. Copy dumps to persistent external storage.",[52,1363,1365],{"id":1364},"managed-postgresql-services","Managed PostgreSQL services",[16,1367,1368],{},"Provider snapshots are useful, but application teams often still want logical dumps because they are portable and easier to test in another environment. Snapshots alone may not give you the workflow you need for app-level recovery drills.",[52,1370,1372],{"id":1371},"backups-do-not-include-django-media-files","Backups do not include Django media files",[16,1374,1375],{},"PostgreSQL backups do not cover uploaded media. If users upload files, your disaster recovery plan must include media storage as well. Static files are usually rebuildable during deploy; media files usually are not.",[52,1377,1379],{"id":1378},"version-compatibility","Version compatibility",[16,1381,1382],{},"Use PostgreSQL client tools that are compatible with your server version. Test backup and restore with the same major PostgreSQL version family you plan to use in production, or validate the upgrade path separately before an incident. Mismatched versions can cause restore problems or unsupported dump behavior.",[11,1384,1386],{"id":1385},"internal-links","Internal links",[16,1388,1389],{},"To go deeper, see:",[63,1391,1392,1399,1405,1411],{},[66,1393,1394],{},[1395,1396,1398],"a",{"href":1397},"\u002Foptimize\u002Fbackup-and-restore-postgres-for-django","Logical vs Physical PostgreSQL Backups for Django Deployments",[66,1400,1401],{},[1395,1402,1404],{"href":1403},"\u002Fdeploy\u002Fconnect-django-to-postgresql-production","How to Deploy Django with PostgreSQL and Gunicorn",[66,1406,1407],{},[1395,1408,1410],{"href":1409},"\u002Foptimize\u002Fdjango-migrations-deployment-strategy","How to Run Django Migrations Safely in Production",[66,1412,1413],{},[1395,1414,1416],{"href":1415},"\u002Foptimize\u002Frollback-django-release-safely","How to Roll Back a Failed Django Deploy",[11,1418,1420],{"id":1419},"faq","FAQ",[52,1422,1424],{"id":1423},"how-often-should-i-back-up-a-postgresql-database-for-a-django-app","How often should I back up a PostgreSQL database for a Django app?",[16,1426,1427,1428,1430],{},"That depends on acceptable data loss. Many apps need at least daily backups plus a pre-deploy backup before migrations or risky releases. If losing even a few minutes of data is unacceptable, scheduled ",[20,1429,22],{}," alone is usually not enough.",[52,1432,1434,1435,1437],{"id":1433},"should-i-use-pg_dump-or-snapshots-for-django-production-backups","Should I use ",[20,1436,22],{}," or snapshots for Django production backups?",[16,1439,1440,1442],{},[20,1441,22],{}," is a strong default for application-level, portable backups. Infrastructure snapshots can help with host recovery, but they are not always as portable or easy to test at the application level. Many teams use both.",[52,1444,1446],{"id":1445},"can-i-restore-a-postgresql-backup-to-a-different-server-or-environment","Can I restore a PostgreSQL backup to a different server or environment?",[16,1448,1449],{},"Yes. That is one of the main advantages of logical backups. In fact, restoring to a separate database or staging server is the safest way to verify that your backups are actually usable.",[52,1451,1453],{"id":1452},"what-should-i-verify-after-restoring-a-django-postgresql-database","What should I verify after restoring a Django PostgreSQL database?",[16,1455,1456],{},"At minimum, verify:",[63,1458,1459,1462,1468,1471,1474],{},[66,1460,1461],{},"expected tables exist",[66,1463,1464,1467],{},[20,1465,1466],{},"django_migrations"," looks correct",[66,1469,1470],{},"required extensions are present",[66,1472,1473],{},"Django can connect successfully",[66,1475,1476],{},"admin login and critical app flows work",[52,1478,1480],{"id":1479},"do-postgresql-backups-cover-uploaded-media-files-in-django","Do PostgreSQL backups cover uploaded media files in Django?",[16,1482,1483],{},"No. Database backups do not include uploaded files stored on disk or object storage. Your recovery plan must include both PostgreSQL data and Django media storage.",[1485,1486,1487],"style",{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":111,"searchDepth":149,"depth":149,"links":1489},[1490,1491,1492,1493,1498,1499,1505,1510,1516,1517,1522,1525,1526,1532,1533],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":46,"depth":136,"text":47,"children":1494},[1495,1497],{"id":54,"depth":149,"text":1496},"Use pg_dump for routine Django backups",{"id":87,"depth":149,"text":88},{"id":97,"depth":136,"text":98},{"id":272,"depth":136,"text":273,"children":1500},[1501,1502,1503,1504],{"id":279,"depth":149,"text":280},{"id":394,"depth":149,"text":395},{"id":518,"depth":149,"text":519},{"id":614,"depth":149,"text":615},{"id":628,"depth":136,"text":629,"children":1506},[1507,1508,1509],{"id":635,"depth":149,"text":636},{"id":662,"depth":149,"text":663},{"id":733,"depth":149,"text":734},{"id":757,"depth":136,"text":758,"children":1511},[1512,1513,1514,1515],{"id":764,"depth":149,"text":765},{"id":824,"depth":149,"text":825},{"id":893,"depth":149,"text":894},{"id":976,"depth":149,"text":977},{"id":1097,"depth":136,"text":1098},{"id":1160,"depth":136,"text":1161,"children":1518},[1519,1520,1521],{"id":1167,"depth":149,"text":1168},{"id":1192,"depth":149,"text":1193},{"id":1216,"depth":149,"text":1217},{"id":1258,"depth":136,"text":1259,"children":1523},[1524],{"id":1313,"depth":149,"text":1314},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":1527},[1528,1529,1530,1531],{"id":1340,"depth":149,"text":1341},{"id":1364,"depth":149,"text":1365},{"id":1371,"depth":149,"text":1372},{"id":1378,"depth":149,"text":1379},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":1534},[1535,1536,1538,1539,1540],{"id":1423,"depth":149,"text":1424},{"id":1433,"depth":149,"text":1537},"Should I use pg_dump or snapshots for Django production backups?",{"id":1445,"depth":149,"text":1446},{"id":1452,"depth":149,"text":1453},{"id":1479,"depth":149,"text":1480},"Operations","A Django app is only recoverable if its PostgreSQL data is recoverable. In production, that means more than occasionally running pg_dump.","intermediate","md","MOFU","transactional",{},"\u002Fbackup-and-restore-postgres-for-django","11",[1409,1551,1552],"\u002Foptimize\u002Fdjango-deployment-script-bash-guide","\u002Foptimize\u002Fdjango-log-rotation-linux","conversion","optimize",{"title":6,"description":1542},[1557,1558],"django","postgresql","backup-and-restore-postgres-for-django",[1557,1558],"task","lfGPA7gutIvZC6E13S-uyRGlY7hvtZG0F26IM3njiFQ",{"id":1564,"title":1565,"body":1566,"category":3088,"description":3089,"difficulty":3090,"extension":1544,"funnel_stage":1545,"intent":3091,"meta":3092,"navigation":309,"path":3093,"priority":3094,"related":3095,"role":3097,"section":3098,"seo":3099,"stack":3100,"stem":3102,"tags":3103,"type":3104,"__hash__":3105},"articles\u002Fbest-hosting-options-for-django-deployment.md","Best Hosting Options for Django Deployment in 2026",{"type":8,"value":1567,"toc":3058},[1568,1570,1577,1580,1600,1603,1620,1627,1629,1636,1643,1664,1671,1677,1681,1757,1759,1763,1766,1798,1801,1842,1845,1849,1854,1857,1871,1874,1888,1891,1914,1917,1921,1923,1937,1940,1962,1965,2018,2024,2149,2152,2324,2327,2364,2367,2395,2398,2431,2434,2457,2460,2464,2466,2480,2483,2610,2613,2624,2627,2641,2644,2648,2651,2665,2668,2691,2694,2698,2701,2752,2755,2801,2805,2831,2835,2838,2855,2858,2875,2878,2880,2883,2890,2895,2900,2905,2909,2912,2914,2971,2973,2980,2987,2994,3001,3008,3015,3017,3021,3024,3028,3031,3035,3041,3045,3048,3052,3055],[11,1569,14],{"id":13},[16,1571,1572,1573,1576],{},"Choosing the ",[1226,1574,1575],{},"best hosting for Django deployment"," is not about finding the cheapest server or the most popular cloud. It is about matching Django’s production requirements to the amount of operational work your team can safely handle.",[16,1578,1579],{},"The wrong choice creates predictable problems:",[63,1581,1582,1585,1588,1591,1594,1597],{},[66,1583,1584],{},"deploys that break during migrations",[66,1586,1587],{},"missing TLS, backups, or rollback paths",[66,1589,1590],{},"poor support for background workers and scheduled jobs",[66,1592,1593],{},"static and media files handled incorrectly",[66,1595,1596],{},"weak logging and health checks",[66,1598,1599],{},"unmanaged OS and security patching",[16,1601,1602],{},"What “best” means depends on a few practical questions:",[63,1604,1605,1608,1611,1614,1617],{},[66,1606,1607],{},"Do you want to manage Linux servers yourself?",[66,1609,1610],{},"Do you need Docker-based deploys?",[66,1612,1613],{},"Will the app need Redis, Celery, cron jobs, and object storage?",[66,1615,1616],{},"Is your team optimizing for speed, control, or low monthly cost?",[66,1618,1619],{},"Do you need simple rollback and repeatable releases?",[16,1621,1622,1623,1626],{},"This guide compares ",[1226,1624,1625],{},"hosting models"," for Django production rather than ranking individual vendors. For most teams, the safest answer is not “the most powerful host.” It is the hosting model that supports your app’s real architecture with the least operational risk.",[11,1628,30],{"id":29},[16,1630,1631,1632,1635],{},"If you are deploying your first production Django app, a ",[1226,1633,1634],{},"managed PaaS"," is usually the best hosting choice. It reduces setup risk, handles TLS and app runtime basics, and gives a simpler deploy flow.",[16,1637,1638,1639,1642],{},"If you want lower long-term infrastructure cost and can manage Linux securely, use a ",[1226,1640,1641],{},"VPS"," with:",[63,1644,1645,1648,1651,1655,1658,1661],{},[66,1646,1647],{},"Nginx",[66,1649,1650],{},"Gunicorn or Uvicorn",[66,1652,1653],{},[20,1654,1277],{},[66,1656,1657],{},"managed PostgreSQL or local PostgreSQL, depending on risk tolerance",[66,1659,1660],{},"Redis if needed",[66,1662,1663],{},"backups, monitoring, and firewall rules",[16,1665,1666,1667,1670],{},"If your team already builds containers and wants repeatable CI\u002FCD, choose ",[1226,1668,1669],{},"managed container hosting",". If you do not already have good container practices, it adds complexity without solving your main deployment problems.",[16,1672,55,1673,1676],{},[1226,1674,1675],{},"Kubernetes"," only when you already need orchestration features, multi-service scheduling, stronger isolation patterns, or team-wide container operations maturity.",[52,1678,1680],{"id":1679},"recommendation-matrix","Recommendation matrix",[1682,1683,1684,1700],"table",{},[1685,1686,1687],"thead",{},[1688,1689,1690,1694,1697],"tr",{},[1691,1692,1693],"th",{},"App\u002Fteam situation",[1691,1695,1696],{},"Best fit",[1691,1698,1699],{},"Why",[1701,1702,1703,1715,1725,1736,1746],"tbody",{},[1688,1704,1705,1709,1712],{},[1706,1707,1708],"td",{},"Small internal app, brochure site, first production deploy",[1706,1710,1711],{},"Managed PaaS",[1706,1713,1714],{},"Fastest safe path",[1688,1716,1717,1720,1722],{},[1706,1718,1719],{},"Small SaaS, cost-sensitive, one or two engineers",[1706,1721,1641],{},[1706,1723,1724],{},"More control, lower base cost",[1688,1726,1727,1730,1733],{},[1706,1728,1729],{},"Django + worker + Redis + Docker-based CI\u002FCD",[1706,1731,1732],{},"Managed container hosting",[1706,1734,1735],{},"Repeatable image-based deploys",[1688,1737,1738,1741,1743],{},[1706,1739,1740],{},"Multi-service platform, strong ops maturity, HA requirements",[1706,1742,1675],{},[1706,1744,1745],{},"Advanced orchestration and scaling",[1688,1747,1748,1751,1754],{},[1706,1749,1750],{},"Shared hosting",[1706,1752,1753],{},"Usually avoid",[1706,1755,1756],{},"Poor fit for modern Django production",[11,1758,43],{"id":42},[52,1760,1762],{"id":1761},"_1-define-what-your-django-app-needs-in-production","1. Define what your Django app needs in production",[16,1764,1765],{},"Before choosing a host, list the required components:",[63,1767,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795],{},[66,1769,1770],{},"web process: Gunicorn or Uvicorn behind a reverse proxy",[66,1772,1773],{},"PostgreSQL",[66,1775,1776],{},"Redis, if using Celery, caching, or Channels",[66,1778,1779],{},"static file handling",[66,1781,1782],{},"media file storage",[66,1784,1785],{},"TLS termination",[66,1787,1788],{},"secrets management",[66,1790,1791],{},"logs and health checks",[66,1793,1794],{},"backups and restore testing",[66,1796,1797],{},"rollback path",[16,1799,1800],{},"Run Django’s baseline production checks locally:",[106,1802,1804],{"className":108,"code":1803,"language":110,"meta":111,"style":111},"python manage.py check --deploy\npython manage.py migrate --plan\npython manage.py collectstatic --noinput\n",[20,1805,1806,1818,1830],{"__ignoreMap":111},[115,1807,1808,1810,1812,1815],{"class":117,"line":118},[115,1809,1114],{"class":262},[115,1811,1117],{"class":132},[115,1813,1814],{"class":132}," check",[115,1816,1817],{"class":202}," --deploy\n",[115,1819,1820,1822,1824,1827],{"class":117,"line":136},[115,1821,1114],{"class":262},[115,1823,1117],{"class":132},[115,1825,1826],{"class":132}," migrate",[115,1828,1829],{"class":202}," --plan\n",[115,1831,1832,1834,1836,1839],{"class":117,"line":149},[115,1833,1114],{"class":262},[115,1835,1117],{"class":132},[115,1837,1838],{"class":132}," collectstatic",[115,1840,1841],{"class":202}," --noinput\n",[16,1843,1844],{},"If these checks are already failing, hosting choice is not your first problem.",[52,1846,1848],{"id":1847},"_2-choose-the-hosting-model-that-matches-your-ops-level","2. Choose the hosting model that matches your ops level",[1850,1851,1853],"h4",{"id":1852},"option-a-managed-paas","Option A: Managed PaaS",[16,1855,1856],{},"Choose this when:",[63,1858,1859,1862,1865,1868],{},[66,1860,1861],{},"you want the fastest route to production",[66,1863,1864],{},"your app is mostly a web service plus managed database",[66,1866,1867],{},"you want platform-managed TLS and simple deploys",[66,1869,1870],{},"your team is weak on Linux server administration",[16,1872,1873],{},"Typical topology:",[63,1875,1876,1879,1882,1885],{},[66,1877,1878],{},"Django web service",[66,1880,1881],{},"managed PostgreSQL",[66,1883,1884],{},"object storage for media",[66,1886,1887],{},"optional Redis and worker service",[16,1889,1890],{},"What to verify before choosing a provider:",[63,1892,1893,1896,1899,1902,1905,1908,1911],{},[66,1894,1895],{},"supports multiple services if you need workers",[66,1897,1898],{},"supports environment variables and secret injection",[66,1900,1901],{},"has health checks",[66,1903,1904],{},"has release or deployment history",[66,1906,1907],{},"supports rollback or previous release restore",[66,1909,1910],{},"gives log access",[66,1912,1913],{},"supports persistent or external media storage",[16,1915,1916],{},"Rollback note: on PaaS, rollback is often release-based, which is convenient, but database migrations still need planning. A rollback that restores app code but leaves a destructive schema change in place is not a full recovery plan.",[1850,1918,1920],{"id":1919},"option-b-vps","Option B: VPS",[16,1922,1856],{},[63,1924,1925,1928,1931,1934],{},[66,1926,1927],{},"you want maximum control",[66,1929,1930],{},"you can manage Linux security and patching",[66,1932,1933],{},"you want a straightforward non-Docker production stack",[66,1935,1936],{},"you need predictable architecture without platform constraints",[16,1938,1939],{},"Typical production stack:",[63,1941,1942,1944,1947,1951,1953,1956,1959],{},[66,1943,1647],{},[66,1945,1946],{},"Gunicorn",[66,1948,1949],{},[20,1950,1277],{},[66,1952,1773],{},[66,1954,1955],{},"Redis",[66,1957,1958],{},"UFW or cloud firewall",[66,1960,1961],{},"off-server backups",[16,1963,1964],{},"Useful verification commands:",[106,1966,1968],{"className":108,"code":1967,"language":110,"meta":111,"style":111},"uname -a\nsystemctl status gunicorn\nsystemctl status nginx\nsudo ss -tulpn\nsudo ufw status\n",[20,1969,1970,1977,1988,1997,2008],{"__ignoreMap":111},[115,1971,1972,1975],{"class":117,"line":118},[115,1973,1974],{"class":262},"uname",[115,1976,206],{"class":202},[115,1978,1979,1982,1985],{"class":117,"line":136},[115,1980,1981],{"class":262},"systemctl",[115,1983,1984],{"class":132}," status",[115,1986,1987],{"class":132}," gunicorn\n",[115,1989,1990,1992,1994],{"class":117,"line":149},[115,1991,1981],{"class":262},[115,1993,1984],{"class":132},[115,1995,1996],{"class":132}," nginx\n",[115,1998,1999,2002,2005],{"class":117,"line":162},[115,2000,2001],{"class":262},"sudo",[115,2003,2004],{"class":132}," ss",[115,2006,2007],{"class":202}," -tulpn\n",[115,2009,2010,2012,2015],{"class":117,"line":175},[115,2011,2001],{"class":262},[115,2013,2014],{"class":132}," ufw",[115,2016,2017],{"class":132}," status\n",[16,2019,2020,2021,2023],{},"Representative Gunicorn ",[20,2022,1277],{}," unit structure:",[106,2025,2029],{"className":2026,"code":2027,"language":2028,"meta":111,"style":111},"language-ini shiki shiki-themes github-light github-dark","[Unit]\nDescription=gunicorn daemon\nAfter=network.target\n\n[Service]\nUser=django\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\nEnvironmentFile=\u002Fetc\u002Fmyapp.env\nRuntimeDirectory=gunicorn\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n","ini",[20,2030,2031,2036,2044,2052,2056,2061,2069,2077,2085,2093,2102,2111,2120,2129,2134,2140],{"__ignoreMap":111},[115,2032,2033],{"class":117,"line":118},[115,2034,2035],{"class":262},"[Unit]\n",[115,2037,2038,2041],{"class":117,"line":136},[115,2039,2040],{"class":121},"Description",[115,2042,2043],{"class":125},"=gunicorn daemon\n",[115,2045,2046,2049],{"class":117,"line":149},[115,2047,2048],{"class":121},"After",[115,2050,2051],{"class":125},"=network.target\n",[115,2053,2054],{"class":117,"line":162},[115,2055,310],{"emptyLinePlaceholder":309},[115,2057,2058],{"class":117,"line":175},[115,2059,2060],{"class":262},"[Service]\n",[115,2062,2063,2066],{"class":117,"line":350},[115,2064,2065],{"class":121},"User",[115,2067,2068],{"class":125},"=django\n",[115,2070,2071,2074],{"class":117,"line":365},[115,2072,2073],{"class":121},"Group",[115,2075,2076],{"class":125},"=www-data\n",[115,2078,2079,2082],{"class":117,"line":380},[115,2080,2081],{"class":121},"WorkingDirectory",[115,2083,2084],{"class":125},"=\u002Fsrv\u002Fmyapp\n",[115,2086,2087,2090],{"class":117,"line":487},[115,2088,2089],{"class":121},"EnvironmentFile",[115,2091,2092],{"class":125},"=\u002Fetc\u002Fmyapp.env\n",[115,2094,2096,2099],{"class":117,"line":2095},10,[115,2097,2098],{"class":121},"RuntimeDirectory",[115,2100,2101],{"class":125},"=gunicorn\n",[115,2103,2105,2108],{"class":117,"line":2104},11,[115,2106,2107],{"class":121},"ExecStart",[115,2109,2110],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock\n",[115,2112,2114,2117],{"class":117,"line":2113},12,[115,2115,2116],{"class":121},"Restart",[115,2118,2119],{"class":125},"=on-failure\n",[115,2121,2123,2126],{"class":117,"line":2122},13,[115,2124,2125],{"class":121},"RestartSec",[115,2127,2128],{"class":125},"=5\n",[115,2130,2132],{"class":117,"line":2131},14,[115,2133,310],{"emptyLinePlaceholder":309},[115,2135,2137],{"class":117,"line":2136},15,[115,2138,2139],{"class":262},"[Install]\n",[115,2141,2143,2146],{"class":117,"line":2142},16,[115,2144,2145],{"class":121},"WantedBy",[115,2147,2148],{"class":125},"=multi-user.target\n",[16,2150,2151],{},"Representative Nginx structure:",[106,2153,2157],{"className":2154,"code":2155,"language":2156,"meta":111,"style":111},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 443 ssl http2;\n    server_name example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock:;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n}\n","nginx",[20,2158,2159,2167,2178,2186,2190,2198,2206,2210,2221,2229,2234,2238,2247,2254,2258,2262,2271,2280,2289,2297,2305,2313,2318],{"__ignoreMap":111},[115,2160,2161,2164],{"class":117,"line":118},[115,2162,2163],{"class":121},"server",[115,2165,2166],{"class":125}," {\n",[115,2168,2169,2172,2175],{"class":117,"line":136},[115,2170,2171],{"class":121},"    listen ",[115,2173,2174],{"class":202},"443",[115,2176,2177],{"class":125}," ssl http2;\n",[115,2179,2180,2183],{"class":117,"line":149},[115,2181,2182],{"class":121},"    server_name ",[115,2184,2185],{"class":125},"example.com;\n",[115,2187,2188],{"class":117,"line":162},[115,2189,310],{"emptyLinePlaceholder":309},[115,2191,2192,2195],{"class":117,"line":175},[115,2193,2194],{"class":121},"    ssl_certificate ",[115,2196,2197],{"class":125},"\u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n",[115,2199,2200,2203],{"class":117,"line":350},[115,2201,2202],{"class":121},"    ssl_certificate_key ",[115,2204,2205],{"class":125},"\u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n",[115,2207,2208],{"class":117,"line":365},[115,2209,310],{"emptyLinePlaceholder":309},[115,2211,2212,2215,2218],{"class":117,"line":380},[115,2213,2214],{"class":121},"    location",[115,2216,2217],{"class":262}," \u002Fstatic\u002F ",[115,2219,2220],{"class":125},"{\n",[115,2222,2223,2226],{"class":117,"line":487},[115,2224,2225],{"class":121},"        alias ",[115,2227,2228],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n",[115,2230,2231],{"class":117,"line":2095},[115,2232,2233],{"class":125},"    }\n",[115,2235,2236],{"class":117,"line":2104},[115,2237,310],{"emptyLinePlaceholder":309},[115,2239,2240,2242,2245],{"class":117,"line":2113},[115,2241,2214],{"class":121},[115,2243,2244],{"class":262}," \u002Fmedia\u002F ",[115,2246,2220],{"class":125},[115,2248,2249,2251],{"class":117,"line":2122},[115,2250,2225],{"class":121},[115,2252,2253],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n",[115,2255,2256],{"class":117,"line":2131},[115,2257,2233],{"class":125},[115,2259,2260],{"class":117,"line":2136},[115,2261,310],{"emptyLinePlaceholder":309},[115,2263,2264,2266,2269],{"class":117,"line":2142},[115,2265,2214],{"class":121},[115,2267,2268],{"class":262}," \u002F ",[115,2270,2220],{"class":125},[115,2272,2274,2277],{"class":117,"line":2273},17,[115,2275,2276],{"class":121},"        proxy_pass ",[115,2278,2279],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock:;\n",[115,2281,2283,2286],{"class":117,"line":2282},18,[115,2284,2285],{"class":121},"        proxy_set_header ",[115,2287,2288],{"class":125},"Host $host;\n",[115,2290,2292,2294],{"class":117,"line":2291},19,[115,2293,2285],{"class":121},[115,2295,2296],{"class":125},"X-Forwarded-Host $host;\n",[115,2298,2300,2302],{"class":117,"line":2299},20,[115,2301,2285],{"class":121},[115,2303,2304],{"class":125},"X-Forwarded-Proto $scheme;\n",[115,2306,2308,2310],{"class":117,"line":2307},21,[115,2309,2285],{"class":121},[115,2311,2312],{"class":125},"X-Forwarded-For $proxy_add_x_forwarded_for;\n",[115,2314,2316],{"class":117,"line":2315},22,[115,2317,2233],{"class":125},[115,2319,2321],{"class":117,"line":2320},23,[115,2322,2323],{"class":125},"}\n",[16,2325,2326],{},"Representative environment file keys:",[106,2328,2332],{"className":2329,"code":2330,"language":2331,"meta":111,"style":111},"language-env shiki shiki-themes github-light github-dark","DJANGO_SETTINGS_MODULE=config.settings.production\nSECRET_KEY=generate-a-long-random-secret-and-store-it-outside-version-control\nDATABASE_URL=postgres:\u002F\u002F...\nREDIS_URL=redis:\u002F\u002F...\nALLOWED_HOSTS=example.com,www.example.com\nCSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n","env",[20,2333,2334,2339,2344,2349,2354,2359],{"__ignoreMap":111},[115,2335,2336],{"class":117,"line":118},[115,2337,2338],{},"DJANGO_SETTINGS_MODULE=config.settings.production\n",[115,2340,2341],{"class":117,"line":136},[115,2342,2343],{},"SECRET_KEY=generate-a-long-random-secret-and-store-it-outside-version-control\n",[115,2345,2346],{"class":117,"line":149},[115,2347,2348],{},"DATABASE_URL=postgres:\u002F\u002F...\n",[115,2350,2351],{"class":117,"line":162},[115,2352,2353],{},"REDIS_URL=redis:\u002F\u002F...\n",[115,2355,2356],{"class":117,"line":175},[115,2357,2358],{},"ALLOWED_HOSTS=example.com,www.example.com\n",[115,2360,2361],{"class":117,"line":350},[115,2362,2363],{},"CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n",[16,2365,2366],{},"Critical Django proxy\u002FTLS setting when HTTPS terminates at Nginx:",[106,2368,2371],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},"language-python shiki shiki-themes github-light github-dark","SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n",[20,2372,2373],{"__ignoreMap":111},[115,2374,2375,2378,2381,2384,2387,2389,2392],{"class":117,"line":118},[115,2376,2377],{"class":202},"SECURE_PROXY_SSL_HEADER",[115,2379,2380],{"class":121}," =",[115,2382,2383],{"class":125}," (",[115,2385,2386],{"class":132},"\"HTTP_X_FORWARDED_PROTO\"",[115,2388,1153],{"class":125},[115,2390,2391],{"class":132},"\"https\"",[115,2393,2394],{"class":125},")\n",[16,2396,2397],{},"Common production hardening settings:",[106,2399,2401],{"className":2369,"code":2400,"language":1114,"meta":111,"style":111},"SECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n",[20,2402,2403,2413,2422],{"__ignoreMap":111},[115,2404,2405,2408,2410],{"class":117,"line":118},[115,2406,2407],{"class":202},"SECURE_SSL_REDIRECT",[115,2409,2380],{"class":121},[115,2411,2412],{"class":202}," True\n",[115,2414,2415,2418,2420],{"class":117,"line":136},[115,2416,2417],{"class":202},"SESSION_COOKIE_SECURE",[115,2419,2380],{"class":121},[115,2421,2412],{"class":202},[115,2423,2424,2427,2429],{"class":117,"line":149},[115,2425,2426],{"class":202},"CSRF_COOKIE_SECURE",[115,2428,2380],{"class":121},[115,2430,2412],{"class":202},[16,2432,2433],{},"What you must manage yourself:",[63,2435,2436,2439,2442,2445,2448,2451,2454],{},[66,2437,2438],{},"package updates",[66,2440,2441],{},"firewall and SSH hardening",[66,2443,2444],{},"TLS certificate issuance and renewal",[66,2446,2447],{},"backups",[66,2449,2450],{},"service restarts",[66,2452,2453],{},"monitoring and alerts",[66,2455,2456],{},"log retention",[16,2458,2459],{},"Rollback note: keep previous application releases on disk, use symlink-based deploys or versioned directories, and make migrations reversible where possible. Also back up the database before risky releases. Do not rely on app-code rollback alone if a release includes destructive or backward-incompatible schema changes.",[1850,2461,2463],{"id":2462},"option-c-managed-container-hosting","Option C: Managed container hosting",[16,2465,1856],{},[63,2467,2468,2471,2474,2477],{},[66,2469,2470],{},"your team already builds Docker images",[66,2472,2473],{},"you want immutable deploy artifacts",[66,2475,2476],{},"you need consistent web and worker runtime environments",[66,2478,2479],{},"CI\u002FCD is image-based",[16,2481,2482],{},"Representative service structure:",[106,2484,2488],{"className":2485,"code":2486,"language":2487,"meta":111,"style":111},"language-yaml shiki shiki-themes github-light github-dark","services:\n  web:\n    image: registry.example.com\u002Fmyapp:web\n    env_file: .env\n    healthcheck:\n      test: [\"CMD-SHELL\", \"curl -f http:\u002F\u002Flocalhost:8000\u002Fhealthz || exit 1\"]\n\n  worker:\n    image: registry.example.com\u002Fmyapp:web\n    command: celery -A config worker -l info\n    env_file: .env\n\n  redis:\n    image: redis:7\n","yaml",[20,2489,2490,2499,2506,2517,2527,2534,2553,2557,2564,2572,2582,2590,2594,2601],{"__ignoreMap":111},[115,2491,2492,2496],{"class":117,"line":118},[115,2493,2495],{"class":2494},"s9eBZ","services",[115,2497,2498],{"class":125},":\n",[115,2500,2501,2504],{"class":117,"line":136},[115,2502,2503],{"class":2494},"  web",[115,2505,2498],{"class":125},[115,2507,2508,2511,2514],{"class":117,"line":149},[115,2509,2510],{"class":2494},"    image",[115,2512,2513],{"class":125},": ",[115,2515,2516],{"class":132},"registry.example.com\u002Fmyapp:web\n",[115,2518,2519,2522,2524],{"class":117,"line":162},[115,2520,2521],{"class":2494},"    env_file",[115,2523,2513],{"class":125},[115,2525,2526],{"class":132},".env\n",[115,2528,2529,2532],{"class":117,"line":175},[115,2530,2531],{"class":2494},"    healthcheck",[115,2533,2498],{"class":125},[115,2535,2536,2539,2542,2545,2547,2550],{"class":117,"line":350},[115,2537,2538],{"class":2494},"      test",[115,2540,2541],{"class":125},": [",[115,2543,2544],{"class":132},"\"CMD-SHELL\"",[115,2546,1153],{"class":125},[115,2548,2549],{"class":132},"\"curl -f http:\u002F\u002Flocalhost:8000\u002Fhealthz || exit 1\"",[115,2551,2552],{"class":125},"]\n",[115,2554,2555],{"class":117,"line":365},[115,2556,310],{"emptyLinePlaceholder":309},[115,2558,2559,2562],{"class":117,"line":380},[115,2560,2561],{"class":2494},"  worker",[115,2563,2498],{"class":125},[115,2565,2566,2568,2570],{"class":117,"line":487},[115,2567,2510],{"class":2494},[115,2569,2513],{"class":125},[115,2571,2516],{"class":132},[115,2573,2574,2577,2579],{"class":117,"line":2095},[115,2575,2576],{"class":2494},"    command",[115,2578,2513],{"class":125},[115,2580,2581],{"class":132},"celery -A config worker -l info\n",[115,2583,2584,2586,2588],{"class":117,"line":2104},[115,2585,2521],{"class":2494},[115,2587,2513],{"class":125},[115,2589,2526],{"class":132},[115,2591,2592],{"class":117,"line":2113},[115,2593,310],{"emptyLinePlaceholder":309},[115,2595,2596,2599],{"class":117,"line":2122},[115,2597,2598],{"class":2494},"  redis",[115,2600,2498],{"class":125},[115,2602,2603,2605,2607],{"class":117,"line":2131},[115,2604,2510],{"class":2494},[115,2606,2513],{"class":125},[115,2608,2609],{"class":132},"redis:7\n",[16,2611,2612],{},"Strengths:",[63,2614,2615,2618,2621],{},[66,2616,2617],{},"repeatable deploys",[66,2619,2620],{},"easier parity across environments",[66,2622,2623],{},"clean rollback by redeploying an older image tag",[16,2625,2626],{},"Weaknesses:",[63,2628,2629,2632,2635,2638],{},[66,2630,2631],{},"registry management",[66,2633,2634],{},"network and storage complexity",[66,2636,2637],{},"more moving parts than a VPS",[66,2639,2640],{},"easy to misuse if you still treat containers like mutable servers",[16,2642,2643],{},"Rollback note: image rollback is simpler than server reconfiguration rollback, but schema migrations remain the main risk. Use expand\u002Fcontract migration patterns for safer deploys.",[1850,2645,2647],{"id":2646},"option-d-kubernetes","Option D: Kubernetes",[16,2649,2650],{},"Choose this only when justified:",[63,2652,2653,2656,2659,2662],{},[66,2654,2655],{},"multiple services with independent scaling",[66,2657,2658],{},"strong observability requirements",[66,2660,2661],{},"platform team or serious ops maturity",[66,2663,2664],{},"existing cluster knowledge in the team",[16,2666,2667],{},"For small Django teams, Kubernetes often increases deployment risk instead of reducing it. You must manage:",[63,2669,2670,2673,2676,2679,2682,2685,2688],{},[66,2671,2672],{},"ingress",[66,2674,2675],{},"secrets",[66,2677,2678],{},"persistent volumes",[66,2680,2681],{},"rollout configuration",[66,2683,2684],{},"readiness and liveness probes",[66,2686,2687],{},"metrics and logging",[66,2689,2690],{},"cluster upgrades and security policy",[16,2692,2693],{},"If your main problem is “where to host a Django app safely,” Kubernetes is usually too much.",[52,2695,2697],{"id":2696},"_3-apply-a-minimum-security-baseline-regardless-of-host","3. Apply a minimum security baseline regardless of host",[16,2699,2700],{},"No hosting model removes the need for basic production hardening:",[63,2702,2703,2708,2714,2720,2726,2729,2734,2737,2740,2743,2746,2749],{},[66,2704,2705],{},[20,2706,2707],{},"DEBUG=False",[66,2709,2710,2711],{},"strong ",[20,2712,2713],{},"SECRET_KEY",[66,2715,2716,2717],{},"restricted ",[20,2718,2719],{},"ALLOWED_HOSTS",[66,2721,2722,2723],{},"correct ",[20,2724,2725],{},"CSRF_TRUSTED_ORIGINS",[66,2727,2728],{},"HTTPS enforced",[66,2730,2731,2733],{},[20,2732,2377],{}," set correctly when TLS terminates at a proxy",[66,2735,2736],{},"secure cookies enabled",[66,2738,2739],{},"database not publicly exposed unless required",[66,2741,2742],{},"least-privilege service accounts",[66,2744,2745],{},"off-site backups",[66,2747,2748],{},"centralized or retained logs",[66,2750,2751],{},"health endpoint for deploy verification",[16,2753,2754],{},"Release verification examples:",[106,2756,2758],{"className":108,"code":2757,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\ncurl -f https:\u002F\u002Fexample.com\u002Fhealthz\njournalctl -u gunicorn -n 100 --no-pager\n",[20,2759,2760,2771,2781],{"__ignoreMap":111},[115,2761,2762,2765,2768],{"class":117,"line":118},[115,2763,2764],{"class":262},"curl",[115,2766,2767],{"class":202}," -I",[115,2769,2770],{"class":132}," https:\u002F\u002Fexample.com\n",[115,2772,2773,2775,2778],{"class":117,"line":136},[115,2774,2764],{"class":262},[115,2776,2777],{"class":202}," -f",[115,2779,2780],{"class":132}," https:\u002F\u002Fexample.com\u002Fhealthz\n",[115,2782,2783,2786,2789,2792,2795,2798],{"class":117,"line":149},[115,2784,2785],{"class":262},"journalctl",[115,2787,2788],{"class":202}," -u",[115,2790,2791],{"class":132}," gunicorn",[115,2793,2794],{"class":202}," -n",[115,2796,2797],{"class":202}," 100",[115,2799,2800],{"class":202}," --no-pager\n",[52,2802,2804],{"id":2803},"_4-match-hosting-to-your-app-type","4. Match hosting to your app type",[63,2806,2807,2813,2819,2825],{},[66,2808,2809,2812],{},[1226,2810,2811],{},"Small brochure or internal app:"," managed PaaS first, VPS second.",[66,2814,2815,2818],{},[1226,2816,2817],{},"SaaS with PostgreSQL, Redis, Celery:"," VPS or managed container hosting.",[66,2820,2821,2824],{},[1226,2822,2823],{},"Containerized multi-service product:"," managed container hosting first, Kubernetes later if justified.",[66,2826,2827,2830],{},[1226,2828,2829],{},"Compliance-sensitive or HA environment:"," managed cloud services or Kubernetes only if your team can support them properly.",[52,2832,2834],{"id":2833},"_5-plan-rollback-before-the-first-production-deploy","5. Plan rollback before the first production deploy",[16,2836,2837],{},"Whatever host you choose, define rollback in advance:",[63,2839,2840,2843,2846,2849,2852],{},[66,2841,2842],{},"previous app release available",[66,2844,2845],{},"database backup before risky migration",[66,2847,2848],{},"reverse migration or compatibility plan",[66,2850,2851],{},"post-deploy checks",[66,2853,2854],{},"clear fail criteria and revert command",[16,2856,2857],{},"A safe release order is usually:",[1173,2859,2860,2863,2866,2869,2872],{},[66,2861,2862],{},"deploy backward-compatible code",[66,2864,2865],{},"run compatible schema changes",[66,2867,2868],{},"verify health and logs",[66,2870,2871],{},"switch traffic fully",[66,2873,2874],{},"remove old code paths only in a later release",[16,2876,2877],{},"A host is not “good for Django production” if rollback is an afterthought.",[11,2879,1321],{"id":1320},[16,2881,2882],{},"Django hosting decisions are really deployment workflow decisions.",[16,2884,2885,2886,2889],{},"A ",[1226,2887,2888],{},"PaaS"," is best when reducing operational burden matters more than low-level control. It is the safest default for beginners because it removes many failure points around TLS, service supervision, and basic runtime management.",[16,2891,2885,2892,2894],{},[1226,2893,1641],{}," is often the best cost-control option because Django runs well on a simple Linux stack. But the savings are real only if you can maintain the server properly. A cheap VPS with no backup, patching, or monitoring is not reliable hosting.",[16,2896,2897,2899],{},[1226,2898,1732],{}," is a good middle ground for teams using Docker. It gives reproducible runtime environments and simpler CI\u002FCD, but only if the team already understands image builds, env injection, health checks, and external state management.",[16,2901,2902,2904],{},[1226,2903,1675],{}," is not the default answer for Django. It solves real problems, but only advanced teams benefit from that complexity.",[52,2906,2908],{"id":2907},"when-manual-setup-becomes-repetitive","When manual setup becomes repetitive",[16,2910,2911],{},"Once you are provisioning the same VPS baseline, Nginx config, Gunicorn unit, env file, and deploy verification steps repeatedly, that is a good point to convert the process into reusable scripts or templates. The first automation targets should be server bootstrap, config generation, health checks, and rollback commands. That reduces drift without changing the underlying architecture.",[11,2913,1337],{"id":1336},[63,2915,2916,2922,2928,2934,2940,2946,2952,2965],{},[66,2917,2918,2921],{},[1226,2919,2920],{},"Static vs media files:"," static files can be collected and served by Nginx or a CDN; user-uploaded media should usually live in object storage or other persistent storage, especially on PaaS and container platforms.",[66,2923,2924,2927],{},[1226,2925,2926],{},"Migrations:"," hosting choice does not remove migration risk. Treat destructive schema changes carefully.",[66,2929,2930,2933],{},[1226,2931,2932],{},"Proxy headers:"," if TLS terminates at the proxy or platform edge, Django must be told how to detect secure requests correctly.",[66,2935,2936,2939],{},[1226,2937,2938],{},"Workers and scheduled jobs:"," verify that the host supports separate long-running worker processes and cron-like schedules.",[66,2941,2942,2945],{},[1226,2943,2944],{},"Timeouts:"," some PaaS and proxies have request timeout limits that can break slow endpoints.",[66,2947,2948,2951],{},[1226,2949,2950],{},"Database placement:"," avoid putting PostgreSQL on the same fragile lifecycle as the app unless you understand the recovery tradeoffs.",[66,2953,2954,2957,2958,2960,2961,2964],{},[1226,2955,2956],{},"CSRF trusted origins:"," ",[20,2959,2725],{}," entries include the scheme, for example ",[20,2962,2963],{},"https:\u002F\u002Fexample.com",". Only add the origins you actually trust.",[66,2966,2967,2970],{},[1226,2968,2969],{},"Shared hosting:"," usually a poor fit for modern Django production because process control, Python environment management, background jobs, and reverse proxy behavior are limited.",[11,2972,1386],{"id":1385},[16,2974,2975,2976,211],{},"For the architecture behind these hosting choices, read ",[1395,2977,2979],{"href":2978},"\u002Fdeploy\u002Fdjango-static-vs-media-files-production","How Django Production Deployment Works: App Server, Reverse Proxy, Static Files, and Database",[16,2981,2982,2983,211],{},"If you decide on a Linux server, use ",[1395,2984,2986],{"href":2985},"\u002Fdeploy\u002Fdeploy-django-gunicorn-nginx-ubuntu","Deploy Django with Gunicorn and Nginx on Ubuntu",[16,2988,2989,2990,211],{},"If your team prefers containers, see ",[1395,2991,2993],{"href":2992},"\u002Fdeploy\u002Fdeploy-django-docker-compose-production","Deploy Django with Docker Compose in Production",[16,2995,2996,2997,211],{},"Before going live, run through ",[1395,2998,3000],{"href":2999},"\u002Fproduction-checklist\u002Fdjango-deployment-checklist","Django Deployment Checklist for Production",[16,3002,3003,3004,211],{},"Related settings guidance: ",[1395,3005,3007],{"href":3006},"\u002Fproduction-checklist\u002Fdjango-production-settings-checklist","Django Production Settings Checklist (DEBUG, ALLOWED_HOSTS, CSRF)",[16,3009,3010,3011,211],{},"If you are deciding between runtime interfaces, read ",[1395,3012,3014],{"href":3013},"\u002Fdeploy\u002Fdjango-wsgi-vs-asgi","Django WSGI vs ASGI: Which One Should You Deploy?",[11,3016,1420],{"id":1419},[52,3018,3020],{"id":3019},"what-is-the-best-hosting-for-django-deployment-if-i-am-deploying-my-first-production-app","What is the best hosting for Django deployment if I am deploying my first production app?",[16,3022,3023],{},"A managed PaaS is usually the best first choice. It reduces setup risk and gives a simpler deployment path while you learn production operations.",[52,3025,3027],{"id":3026},"should-i-choose-a-vps-or-a-managed-platform-for-django","Should I choose a VPS or a managed platform for Django?",[16,3029,3030],{},"Choose a VPS if you want control and can manage Linux securely. Choose a managed platform if you want faster, lower-risk deployment and do not want to own patching, proxy setup, and service supervision.",[52,3032,3034],{"id":3033},"is-docker-required-for-reliable-django-hosting-in-2026","Is Docker required for reliable Django hosting in 2026?",[16,3036,3037,3038,3040],{},"No. Docker helps with repeatability and CI\u002FCD, but a non-Docker stack with Nginx, Gunicorn, ",[20,3039,1277],{},", PostgreSQL, and good operational practices is still a solid production approach.",[52,3042,3044],{"id":3043},"what-hosting-option-gives-the-easiest-rollback-for-django-releases","What hosting option gives the easiest rollback for Django releases?",[16,3046,3047],{},"Managed container hosting usually gives the cleanest app-code rollback because you can redeploy a previous image tag. But database migrations are still the hard part, so rollback is only safe if schema changes were planned properly.",[52,3049,3051],{"id":3050},"when-should-a-django-team-move-from-simple-hosting-to-container-orchestration","When should a Django team move from simple hosting to container orchestration?",[16,3053,3054],{},"Move to orchestration only when you already have multiple services, scaling needs, stronger runtime isolation requirements, and the operational maturity to support cluster management. Do not adopt Kubernetes just to host a single standard Django app.",[1485,3056,3057],{},"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 .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":3059},[3060,3061,3064,3076,3079,3080,3081],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30,"children":3062},[3063],{"id":1679,"depth":149,"text":1680},{"id":42,"depth":136,"text":43,"children":3065},[3066,3067,3073,3074,3075],{"id":1761,"depth":149,"text":1762},{"id":1847,"depth":149,"text":1848,"children":3068},[3069,3070,3071,3072],{"id":1852,"depth":162,"text":1853},{"id":1919,"depth":162,"text":1920},{"id":2462,"depth":162,"text":2463},{"id":2646,"depth":162,"text":2647},{"id":2696,"depth":149,"text":2697},{"id":2803,"depth":149,"text":2804},{"id":2833,"depth":149,"text":2834},{"id":1320,"depth":136,"text":1321,"children":3077},[3078],{"id":2907,"depth":149,"text":2908},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":3082},[3083,3084,3085,3086,3087],{"id":3019,"depth":149,"text":3020},{"id":3026,"depth":149,"text":3027},{"id":3033,"depth":149,"text":3034},{"id":3043,"depth":149,"text":3044},{"id":3050,"depth":149,"text":3051},"Deployment","Choosing the best hosting for Django deployment is not about finding the cheapest server or the most popular cloud.","beginner","commercial",{},"\u002Fbest-hosting-options-for-django-deployment","3",[3096,3013,2978],"\u002Fdeploy\u002Fconfigure-managed-postgres-for-django","entry","deploy",{"title":1565,"description":3089},[1557,3101],"cloud","best-hosting-options-for-django-deployment",[1557,3101],"foundation","2dr853o0dhlUS2eqtaPE2mGZVPD5CKiTe7M1FBtVDNY",{"id":3107,"title":3108,"body":3109,"category":4543,"description":4544,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":4547,"navigation":309,"path":4548,"priority":4549,"related":4550,"role":4552,"section":4553,"seo":4554,"stack":4555,"stem":4556,"tags":4557,"type":4558,"__hash__":4559},"articles\u002Ffix-csrf-verification-failed-django-production.md","CSRF Verification Failed in Django Production: How to Fix It",{"type":8,"value":3110,"toc":4518},[3111,3113,3122,3125,3153,3156,3170,3173,3175,3182,3217,3220,3237,3239,3243,3246,3261,3264,3283,3286,3308,3311,3325,3328,3466,3469,3486,3489,3511,3517,3521,3527,3559,3565,3596,3599,3630,3639,3655,3666,3669,3674,3678,3685,3688,3788,3791,3833,3836,3868,3871,3892,3895,3922,3925,3951,3958,3963,3967,3970,3973,3990,3993,4019,4022,4036,4043,4046,4060,4067,4070,4078,4082,4085,4111,4118,4121,4134,4137,4140,4145,4169,4173,4176,4193,4196,4214,4230,4233,4241,4248,4252,4255,4266,4269,4283,4286,4289,4292,4303,4306,4316,4320,4328,4330,4333,4344,4347,4353,4361,4363,4411,4413,4420,4430,4436,4439,4457,4459,4463,4466,4475,4484,4488,4497,4501,4508,4512,4515],[11,3112,14],{"id":13},[16,3114,3115,3118,3119,211],{},[20,3116,3117],{},"CSRF Verification Failed"," in Django production usually appears after a deployment change that affects domains, HTTPS, cookies, or proxy headers. The app may work locally and even serve pages normally in production, but form submissions, admin login, or other POST requests fail with ",[20,3120,3121],{},"403 Forbidden",[16,3123,3124],{},"This commonly happens when:",[63,3126,3127,3134,3137,3140,3150],{},[66,3128,3129,3130,3133],{},"you move from ",[20,3131,3132],{},"localhost"," to a real domain",[66,3135,3136],{},"HTTPS is terminated at Nginx, Caddy, or a load balancer",[66,3138,3139],{},"the app runs behind a reverse proxy",[66,3141,3142,3143,3146,3147],{},"the canonical host changes between ",[20,3144,3145],{},"example.com"," and ",[20,3148,3149],{},"www.example.com",[66,3151,3152],{},"the CSRF cookie is not being set or not being returned by the browser",[16,3154,3155],{},"Typical symptoms:",[63,3157,3158,3161,3164,3167],{},[66,3159,3160],{},"Django admin login form fails in production",[66,3162,3163],{},"GET requests work, but POST requests return 403",[66,3165,3166],{},"sessions seem fine, but form submission fails",[66,3168,3169],{},"the issue starts only after enabling HTTPS or adding a proxy",[16,3171,3172],{},"The goal is to find the exact failure reason and fix the deployment safely without weakening CSRF protection.",[11,3174,30],{"id":29},[16,3176,3177,3178,3181],{},"The most common fixes for ",[1226,3179,3180],{},"CSRF verification failed in Django production"," are:",[1173,3183,3184,3190,3198,3207,3214],{},[66,3185,3186,3187,3189],{},"make sure ",[20,3188,2719],{}," includes the active production hostnames",[66,3191,3192,3193,3195,3196],{},"set ",[20,3194,2725],{}," with the full scheme, such as ",[20,3197,2963],{},[66,3199,3200,3201,3204,3205],{},"if TLS is terminated at a proxy, forward ",[20,3202,3203],{},"X-Forwarded-Proto"," and set ",[20,3206,2377],{},[66,3208,3209,3210,3213],{},"verify the browser receives and sends the ",[20,3211,3212],{},"csrftoken"," cookie",[66,3215,3216],{},"confirm the request origin, host, redirect target, and scheme all match your real production URL",[16,3218,3219],{},"Safe order of operations:",[63,3221,3222,3225,3228,3231,3234],{},[66,3223,3224],{},"inspect logs first",[66,3226,3227],{},"verify headers and cookies in the browser",[66,3229,3230],{},"change one setting at a time",[66,3232,3233],{},"test in staging first if possible",[66,3235,3236],{},"keep a known-good settings snapshot for rollback",[11,3238,43],{"id":42},[11,3240,3242],{"id":3241},"_1-identify-the-exact-csrf-failure-reason","1) Identify the exact CSRF failure reason",[16,3244,3245],{},"Start by checking Django logs and app server logs.",[106,3247,3249],{"className":108,"code":3248,"language":110,"meta":111,"style":111},"python manage.py check --deploy\n",[20,3250,3251],{"__ignoreMap":111},[115,3252,3253,3255,3257,3259],{"class":117,"line":118},[115,3254,1114],{"class":262},[115,3256,1117],{"class":132},[115,3258,1814],{"class":132},[115,3260,1817],{"class":202},[16,3262,3263],{},"For systemd deployments:",[106,3265,3267],{"className":108,"code":3266,"language":110,"meta":111,"style":111},"journalctl -u gunicorn -n 100 --no-pager\n",[20,3268,3269],{"__ignoreMap":111},[115,3270,3271,3273,3275,3277,3279,3281],{"class":117,"line":118},[115,3272,2785],{"class":262},[115,3274,2788],{"class":202},[115,3276,2791],{"class":132},[115,3278,2794],{"class":202},[115,3280,2797],{"class":202},[115,3282,2800],{"class":202},[16,3284,3285],{},"For Docker Compose:",[106,3287,3289],{"className":108,"code":3288,"language":110,"meta":111,"style":111},"docker compose logs web --tail=100\n",[20,3290,3291],{"__ignoreMap":111},[115,3292,3293,3296,3299,3302,3305],{"class":117,"line":118},[115,3294,3295],{"class":262},"docker",[115,3297,3298],{"class":132}," compose",[115,3300,3301],{"class":132}," logs",[115,3303,3304],{"class":132}," web",[115,3306,3307],{"class":202}," --tail=100\n",[16,3309,3310],{},"Django often logs more specific reasons than the browser shows, such as:",[63,3312,3313,3316,3319,3322],{},[66,3314,3315],{},"bad origin",[66,3317,3318],{},"referer checking failed",[66,3320,3321],{},"CSRF cookie not set",[66,3323,3324],{},"CSRF token missing or incorrect",[16,3326,3327],{},"If logs are too sparse, add production-safe logging for 403 and security events:",[106,3329,3331],{"className":2369,"code":3330,"language":1114,"meta":111,"style":111},"LOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n        },\n    },\n    \"loggers\": {\n        \"django.security.csrf\": {\n            \"handlers\": [\"console\"],\n            \"level\": \"WARNING\",\n            \"propagate\": False,\n        },\n    },\n}\n",[20,3332,3333,3342,3355,3367,3375,3382,3394,3399,3404,3411,3418,3431,3443,3454,3458,3462],{"__ignoreMap":111},[115,3334,3335,3338,3340],{"class":117,"line":118},[115,3336,3337],{"class":202},"LOGGING",[115,3339,2380],{"class":121},[115,3341,2166],{"class":125},[115,3343,3344,3347,3349,3352],{"class":117,"line":136},[115,3345,3346],{"class":132},"    \"version\"",[115,3348,2513],{"class":125},[115,3350,3351],{"class":202},"1",[115,3353,3354],{"class":125},",\n",[115,3356,3357,3360,3362,3365],{"class":117,"line":149},[115,3358,3359],{"class":132},"    \"disable_existing_loggers\"",[115,3361,2513],{"class":125},[115,3363,3364],{"class":202},"False",[115,3366,3354],{"class":125},[115,3368,3369,3372],{"class":117,"line":162},[115,3370,3371],{"class":132},"    \"handlers\"",[115,3373,3374],{"class":125},": {\n",[115,3376,3377,3380],{"class":117,"line":175},[115,3378,3379],{"class":132},"        \"console\"",[115,3381,3374],{"class":125},[115,3383,3384,3387,3389,3392],{"class":117,"line":350},[115,3385,3386],{"class":132},"            \"class\"",[115,3388,2513],{"class":125},[115,3390,3391],{"class":132},"\"logging.StreamHandler\"",[115,3393,3354],{"class":125},[115,3395,3396],{"class":117,"line":365},[115,3397,3398],{"class":125},"        },\n",[115,3400,3401],{"class":117,"line":380},[115,3402,3403],{"class":125},"    },\n",[115,3405,3406,3409],{"class":117,"line":487},[115,3407,3408],{"class":132},"    \"loggers\"",[115,3410,3374],{"class":125},[115,3412,3413,3416],{"class":117,"line":2095},[115,3414,3415],{"class":132},"        \"django.security.csrf\"",[115,3417,3374],{"class":125},[115,3419,3420,3423,3425,3428],{"class":117,"line":2104},[115,3421,3422],{"class":132},"            \"handlers\"",[115,3424,2541],{"class":125},[115,3426,3427],{"class":132},"\"console\"",[115,3429,3430],{"class":125},"],\n",[115,3432,3433,3436,3438,3441],{"class":117,"line":2113},[115,3434,3435],{"class":132},"            \"level\"",[115,3437,2513],{"class":125},[115,3439,3440],{"class":132},"\"WARNING\"",[115,3442,3354],{"class":125},[115,3444,3445,3448,3450,3452],{"class":117,"line":2122},[115,3446,3447],{"class":132},"            \"propagate\"",[115,3449,2513],{"class":125},[115,3451,3364],{"class":202},[115,3453,3354],{"class":125},[115,3455,3456],{"class":117,"line":2131},[115,3457,3398],{"class":125},[115,3459,3460],{"class":117,"line":2136},[115,3461,3403],{"class":125},[115,3463,3464],{"class":117,"line":2142},[115,3465,2323],{"class":125},[16,3467,3468],{},"Restart the app after changing settings:",[106,3470,3472],{"className":108,"code":3471,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn\n",[20,3473,3474],{"__ignoreMap":111},[115,3475,3476,3478,3481,3484],{"class":117,"line":118},[115,3477,2001],{"class":262},[115,3479,3480],{"class":132}," systemctl",[115,3482,3483],{"class":132}," restart",[115,3485,1987],{"class":132},[16,3487,3488],{},"Or:",[106,3490,3492],{"className":108,"code":3491,"language":110,"meta":111,"style":111},"docker compose up -d --force-recreate web\n",[20,3493,3494],{"__ignoreMap":111},[115,3495,3496,3498,3500,3503,3505,3508],{"class":117,"line":118},[115,3497,3295],{"class":262},[115,3499,3298],{"class":132},[115,3501,3502],{"class":132}," up",[115,3504,1019],{"class":202},[115,3506,3507],{"class":202}," --force-recreate",[115,3509,3510],{"class":132}," web\n",[16,3512,3513,3516],{},[1226,3514,3515],{},"Verification check:"," reproduce the 403 and confirm the logs now show whether it is an origin, referer, or cookie problem.",[11,3518,3520],{"id":3519},"_2-verify-production-domain-and-https-settings","2) Verify production domain and HTTPS settings",[16,3522,3523,3524,3526],{},"Check ",[20,3525,2719],{}," first:",[106,3528,3530],{"className":2369,"code":3529,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\n    \"example.com\",\n    \"www.example.com\",\n]\n",[20,3531,3532,3541,3548,3555],{"__ignoreMap":111},[115,3533,3534,3536,3538],{"class":117,"line":118},[115,3535,2719],{"class":202},[115,3537,2380],{"class":121},[115,3539,3540],{"class":125}," [\n",[115,3542,3543,3546],{"class":117,"line":136},[115,3544,3545],{"class":132},"    \"example.com\"",[115,3547,3354],{"class":125},[115,3549,3550,3553],{"class":117,"line":149},[115,3551,3552],{"class":132},"    \"www.example.com\"",[115,3554,3354],{"class":125},[115,3556,3557],{"class":117,"line":162},[115,3558,2552],{"class":125},[16,3560,3561,3562,3564],{},"Then check ",[20,3563,2725],{},". In modern Django, this must include the full scheme:",[106,3566,3568],{"className":2369,"code":3567,"language":1114,"meta":111,"style":111},"CSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fexample.com\",\n    \"https:\u002F\u002Fwww.example.com\",\n]\n",[20,3569,3570,3578,3585,3592],{"__ignoreMap":111},[115,3571,3572,3574,3576],{"class":117,"line":118},[115,3573,2725],{"class":202},[115,3575,2380],{"class":121},[115,3577,3540],{"class":125},[115,3579,3580,3583],{"class":117,"line":136},[115,3581,3582],{"class":132},"    \"https:\u002F\u002Fexample.com\"",[115,3584,3354],{"class":125},[115,3586,3587,3590],{"class":117,"line":149},[115,3588,3589],{"class":132},"    \"https:\u002F\u002Fwww.example.com\"",[115,3591,3354],{"class":125},[115,3593,3594],{"class":117,"line":162},[115,3595,2552],{"class":125},[16,3597,3598],{},"If you use subdomains, add the ones that actually serve forms:",[106,3600,3602],{"className":2369,"code":3601,"language":1114,"meta":111,"style":111},"CSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fapp.example.com\",\n    \"https:\u002F\u002Fadmin.example.com\",\n]\n",[20,3603,3604,3612,3619,3626],{"__ignoreMap":111},[115,3605,3606,3608,3610],{"class":117,"line":118},[115,3607,2725],{"class":202},[115,3609,2380],{"class":121},[115,3611,3540],{"class":125},[115,3613,3614,3617],{"class":117,"line":136},[115,3615,3616],{"class":132},"    \"https:\u002F\u002Fapp.example.com\"",[115,3618,3354],{"class":125},[115,3620,3621,3624],{"class":117,"line":149},[115,3622,3623],{"class":132},"    \"https:\u002F\u002Fadmin.example.com\"",[115,3625,3354],{"class":125},[115,3627,3628],{"class":117,"line":162},[115,3629,2552],{"class":125},[16,3631,3632,3633,3635,3636,3638],{},"Do not assume ",[20,3634,2719],{}," replaces ",[20,3637,2725],{},". They solve different problems:",[63,3640,3641,3650],{},[66,3642,3643,3645,3646,3649],{},[20,3644,2719],{}," controls which ",[20,3647,3648],{},"Host"," headers Django accepts",[66,3651,3652,3654],{},[20,3653,2725],{}," controls which origins are trusted for unsafe requests",[16,3656,3657,3658,3661,3662,3665],{},"Also check redirect behavior. If users load ",[20,3659,3660],{},"http:\u002F\u002Fexample.com",", then get redirected to ",[20,3663,3664],{},"https:\u002F\u002Fwww.example.com",", make sure your forms submit to the final canonical domain and not an earlier host.",[16,3667,3668],{},"Do not add extra origins just to suppress the error. Trust only the exact HTTPS origins that legitimately serve unsafe requests.",[16,3670,3671,3673],{},[1226,3672,3515],{}," open the form page in the browser and confirm the address bar host matches the configured canonical host. Then confirm your settings include that host and scheme.",[11,3675,3677],{"id":3676},"_3-fix-reverse-proxy-and-tls-header-handling","3) Fix reverse proxy and TLS header handling",[16,3679,3680,3681,3684],{},"A very common cause of ",[1226,3682,3683],{},"Django Nginx CSRF verification failed"," issues is that Django does not realize the original request was HTTPS.",[16,3686,3687],{},"If Nginx terminates TLS and proxies to Gunicorn on HTTP, forward the right headers:",[106,3689,3691],{"className":2154,"code":3690,"language":2156,"meta":111,"style":111},"server {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,3692,3693,3699,3707,3714,3718,3724,3730,3734,3742,3749,3755,3761,3768,3774,3780,3784],{"__ignoreMap":111},[115,3694,3695,3697],{"class":117,"line":118},[115,3696,2163],{"class":121},[115,3698,2166],{"class":125},[115,3700,3701,3703,3705],{"class":117,"line":136},[115,3702,2171],{"class":121},[115,3704,2174],{"class":202},[115,3706,2177],{"class":125},[115,3708,3709,3711],{"class":117,"line":149},[115,3710,2182],{"class":121},[115,3712,3713],{"class":125},"example.com www.example.com;\n",[115,3715,3716],{"class":117,"line":162},[115,3717,310],{"emptyLinePlaceholder":309},[115,3719,3720,3722],{"class":117,"line":175},[115,3721,2194],{"class":121},[115,3723,2197],{"class":125},[115,3725,3726,3728],{"class":117,"line":350},[115,3727,2202],{"class":121},[115,3729,2205],{"class":125},[115,3731,3732],{"class":117,"line":365},[115,3733,310],{"emptyLinePlaceholder":309},[115,3735,3736,3738,3740],{"class":117,"line":380},[115,3737,2214],{"class":121},[115,3739,2268],{"class":262},[115,3741,2220],{"class":125},[115,3743,3744,3746],{"class":117,"line":487},[115,3745,2276],{"class":121},[115,3747,3748],{"class":125},"http:\u002F\u002F127.0.0.1:8000;\n",[115,3750,3751,3753],{"class":117,"line":2095},[115,3752,2285],{"class":121},[115,3754,2288],{"class":125},[115,3756,3757,3759],{"class":117,"line":2104},[115,3758,2285],{"class":121},[115,3760,2296],{"class":125},[115,3762,3763,3765],{"class":117,"line":2113},[115,3764,2285],{"class":121},[115,3766,3767],{"class":125},"X-Real-IP $remote_addr;\n",[115,3769,3770,3772],{"class":117,"line":2122},[115,3771,2285],{"class":121},[115,3773,2312],{"class":125},[115,3775,3776,3778],{"class":117,"line":2131},[115,3777,2285],{"class":121},[115,3779,2304],{"class":125},[115,3781,3782],{"class":117,"line":2136},[115,3783,2233],{"class":125},[115,3785,3786],{"class":117,"line":2142},[115,3787,2323],{"class":125},[16,3789,3790],{},"If you redirect HTTP to HTTPS, keep host behavior consistent:",[106,3792,3794],{"className":2154,"code":3793,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n",[20,3795,3796,3802,3812,3818,3829],{"__ignoreMap":111},[115,3797,3798,3800],{"class":117,"line":118},[115,3799,2163],{"class":121},[115,3801,2166],{"class":125},[115,3803,3804,3806,3809],{"class":117,"line":136},[115,3805,2171],{"class":121},[115,3807,3808],{"class":202},"80",[115,3810,3811],{"class":125},";\n",[115,3813,3814,3816],{"class":117,"line":149},[115,3815,2182],{"class":121},[115,3817,3713],{"class":125},[115,3819,3820,3823,3826],{"class":117,"line":162},[115,3821,3822],{"class":121},"    return",[115,3824,3825],{"class":202}," 301",[115,3827,3828],{"class":125}," https:\u002F\u002F$host$request_uri;\n",[115,3830,3831],{"class":117,"line":175},[115,3832,2323],{"class":125},[16,3834,3835],{},"In Django settings, trust the forwarded proto header:",[106,3837,3839],{"className":2369,"code":3838,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n# Only set this when Django is behind a trusted reverse proxy\n# that correctly sets and sanitizes X-Forwarded-Proto.\n",[20,3840,3841,3857,3863],{"__ignoreMap":111},[115,3842,3843,3845,3847,3849,3851,3853,3855],{"class":117,"line":118},[115,3844,2377],{"class":202},[115,3846,2380],{"class":121},[115,3848,2383],{"class":125},[115,3850,2386],{"class":132},[115,3852,1153],{"class":125},[115,3854,2391],{"class":132},[115,3856,2394],{"class":125},[115,3858,3859],{"class":117,"line":136},[115,3860,3862],{"class":3861},"sJ8bj","# Only set this when Django is behind a trusted reverse proxy\n",[115,3864,3865],{"class":117,"line":149},[115,3866,3867],{"class":3861},"# that correctly sets and sanitizes X-Forwarded-Proto.\n",[16,3869,3870],{},"For secure cookie behavior in production:",[106,3872,3874],{"className":2369,"code":3873,"language":1114,"meta":111,"style":111},"CSRF_COOKIE_SECURE = True\nSESSION_COOKIE_SECURE = True\n",[20,3875,3876,3884],{"__ignoreMap":111},[115,3877,3878,3880,3882],{"class":117,"line":118},[115,3879,2426],{"class":202},[115,3881,2380],{"class":121},[115,3883,2412],{"class":202},[115,3885,3886,3888,3890],{"class":117,"line":136},[115,3887,2417],{"class":202},[115,3889,2380],{"class":121},[115,3891,2412],{"class":202},[16,3893,3894],{},"Test Nginx config before reloading:",[106,3896,3898],{"className":108,"code":3897,"language":110,"meta":111,"style":111},"sudo nginx -t && sudo systemctl reload nginx\n",[20,3899,3900],{"__ignoreMap":111},[115,3901,3902,3904,3907,3910,3913,3915,3917,3920],{"class":117,"line":118},[115,3903,2001],{"class":262},[115,3905,3906],{"class":132}," nginx",[115,3908,3909],{"class":202}," -t",[115,3911,3912],{"class":125}," && ",[115,3914,2001],{"class":262},[115,3916,3480],{"class":132},[115,3918,3919],{"class":132}," reload",[115,3921,1996],{"class":132},[16,3923,3924],{},"To inspect how Django interprets forwarded scheme headers at the app layer, a direct app-level test can help, but it does not replace testing through the real reverse proxy path:",[106,3926,3928],{"className":108,"code":3927,"language":110,"meta":111,"style":111},"curl -k -H \"Host: example.com\" -H \"X-Forwarded-Proto: https\" http:\u002F\u002F127.0.0.1:8000\u002F\n",[20,3929,3930],{"__ignoreMap":111},[115,3931,3932,3934,3937,3940,3943,3945,3948],{"class":117,"line":118},[115,3933,2764],{"class":262},[115,3935,3936],{"class":202}," -k",[115,3938,3939],{"class":202}," -H",[115,3941,3942],{"class":132}," \"Host: example.com\"",[115,3944,3939],{"class":202},[115,3946,3947],{"class":132}," \"X-Forwarded-Proto: https\"",[115,3949,3950],{"class":132}," http:\u002F\u002F127.0.0.1:8000\u002F\n",[16,3952,3953,3954,3957],{},"For Caddy, ",[20,3955,3956],{},"reverse_proxy"," usually forwards the required request information, but you should still verify the resulting headers and Django’s HTTPS detection.",[16,3959,3960,3962],{},[1226,3961,3515],{}," after reload and restart, reproduce the request and confirm CSRF logs no longer report bad origin or insecure referer mismatches.",[11,3964,3966],{"id":3965},"_4-confirm-csrf-cookie-and-session-behavior","4) Confirm CSRF cookie and session behavior",[16,3968,3969],{},"Now check whether the browser actually gets the CSRF cookie on the GET request that serves the form.",[16,3971,3972],{},"Use browser dev tools:",[63,3974,3975,3978,3981,3984],{},[66,3976,3977],{},"open the Network tab",[66,3979,3980],{},"load the login or form page",[66,3982,3983],{},"inspect the response headers",[66,3985,3986,3987],{},"look for ",[20,3988,3989],{},"Set-Cookie: csrftoken=...",[16,3991,3992],{},"A quick command-line check for response headers on a real GET request:",[106,3994,3996],{"className":108,"code":3995,"language":110,"meta":111,"style":111},"curl -s -D - -o \u002Fdev\u002Fnull https:\u002F\u002Fexample.com\u002Flogin\u002F\n",[20,3997,3998],{"__ignoreMap":111},[115,3999,4000,4002,4004,4007,4010,4013,4016],{"class":117,"line":118},[115,4001,2764],{"class":262},[115,4003,549],{"class":202},[115,4005,4006],{"class":202}," -D",[115,4008,4009],{"class":132}," -",[115,4011,4012],{"class":202}," -o",[115,4014,4015],{"class":132}," \u002Fdev\u002Fnull",[115,4017,4018],{"class":132}," https:\u002F\u002Fexample.com\u002Flogin\u002F\n",[16,4020,4021],{},"If the cookie is missing, common causes include:",[63,4023,4024,4027,4030,4033],{},[66,4025,4026],{},"the form page was cached incorrectly",[66,4028,4029],{},"the view never rendered a CSRF token",[66,4031,4032],{},"cookie domain settings are too restrictive",[66,4034,4035],{},"HTTPS or cookie settings conflict with the real request path",[16,4037,4038,4039,4042],{},"Only set ",[20,4040,4041],{},"CSRF_COOKIE_DOMAIN"," if you truly need cross-subdomain cookie sharing. Otherwise, leave it unset. A bad value can break cookie delivery.",[16,4044,4045],{},"Example only when required:",[106,4047,4049],{"className":2369,"code":4048,"language":1114,"meta":111,"style":111},"CSRF_COOKIE_DOMAIN = \".example.com\"\n",[20,4050,4051],{"__ignoreMap":111},[115,4052,4053,4055,4057],{"class":117,"line":118},[115,4054,4041],{"class":202},[115,4056,2380],{"class":121},[115,4058,4059],{"class":132}," \".example.com\"\n",[16,4061,4062,4063,4066],{},"If your flow spans subdomains or embedded contexts, review ",[20,4064,4065],{},"SameSite"," behavior separately. For standard same-site Django forms, the default behavior is usually correct.",[16,4068,4069],{},"Also check that no CDN, WAF, or cache layer strips cookies or modifies headers on dynamic form pages.",[16,4071,4072,4074,4075,4077],{},[1226,4073,3515],{}," confirm the GET response sets ",[20,4076,3212],{},", and the subsequent POST request includes that cookie.",[11,4079,4081],{"id":4080},"_5-check-frontend-and-form-submission-details","5) Check frontend and form submission details",[16,4083,4084],{},"For server-rendered Django templates, verify the form contains:",[106,4086,4089],{"className":4087,"code":4088,"language":1557,"meta":111,"style":111},"language-django shiki shiki-themes github-light github-dark","\u003Cform method=\"post\">\n    {% csrf_token %}\n    \u003C!-- fields -->\n\u003C\u002Fform>\n",[20,4090,4091,4096,4101,4106],{"__ignoreMap":111},[115,4092,4093],{"class":117,"line":118},[115,4094,4095],{},"\u003Cform method=\"post\">\n",[115,4097,4098],{"class":117,"line":136},[115,4099,4100],{},"    {% csrf_token %}\n",[115,4102,4103],{"class":117,"line":149},[115,4104,4105],{},"    \u003C!-- fields -->\n",[115,4107,4108],{"class":117,"line":162},[115,4109,4110],{},"\u003C\u002Fform>\n",[16,4112,4113,4114,4117],{},"For AJAX requests, ensure the CSRF token is sent in the ",[20,4115,4116],{},"X-CSRFToken"," header and that the request is going to the correct HTTPS origin.",[16,4119,4120],{},"Also check for mixed-scheme mistakes:",[63,4122,4123,4128],{},[66,4124,4125,4126],{},"page loaded on ",[20,4127,2963],{},[66,4129,4130,4131],{},"form posts to ",[20,4132,4133],{},"http:\u002F\u002Fexample.com\u002F...",[16,4135,4136],{},"That mismatch can trigger referer or origin failures in production.",[16,4138,4139],{},"If your frontend is truly cross-origin, treat it as a separate architecture problem. A same-origin Django template setup and a decoupled frontend are not configured the same way.",[16,4141,4142,4144],{},[1226,4143,3515],{}," inspect the failing POST in browser dev tools and confirm:",[63,4146,4147,4153,4158,4163,4166],{},[66,4148,4149,4152],{},[20,4150,4151],{},"Origin"," is correct",[66,4154,4155,4152],{},[20,4156,4157],{},"Referer",[66,4159,4160,4162],{},[20,4161,3212],{}," cookie is present",[66,4164,4165],{},"token field or header is present",[66,4167,4168],{},"request target is the expected domain and scheme",[11,4170,4172],{"id":4171},"_6-test-the-fix-safely-in-production","6) Test the fix safely in production",[16,4174,4175],{},"After each change, test the full flow:",[1173,4177,4178,4181,4187,4190],{},[66,4179,4180],{},"load the form page",[66,4182,4183,4184,4186],{},"confirm ",[20,4185,3212],{}," is set",[66,4188,4189],{},"submit the form",[66,4191,4192],{},"watch logs during the POST",[16,4194,4195],{},"Useful checks:",[106,4197,4198],{"className":108,"code":3266,"language":110,"meta":111,"style":111},[20,4199,4200],{"__ignoreMap":111},[115,4201,4202,4204,4206,4208,4210,4212],{"class":117,"line":118},[115,4203,2785],{"class":262},[115,4205,2788],{"class":202},[115,4207,2791],{"class":132},[115,4209,2794],{"class":202},[115,4211,2797],{"class":202},[115,4213,2800],{"class":202},[106,4215,4216],{"className":108,"code":3288,"language":110,"meta":111,"style":111},[20,4217,4218],{"__ignoreMap":111},[115,4219,4220,4222,4224,4226,4228],{"class":117,"line":118},[115,4221,3295],{"class":262},[115,4223,3298],{"class":132},[115,4225,3301],{"class":132},[115,4227,3304],{"class":132},[115,4229,3307],{"class":202},[16,4231,4232],{},"Test both:",[63,4234,4235,4238],{},[66,4236,4237],{},"Django admin login",[66,4239,4240],{},"one normal application form POST",[16,4242,4243,4244,4247],{},"If you changed proxy config, also confirm the canonical redirect path works correctly from HTTP to HTTPS and between ",[20,4245,4246],{},"www"," and apex if applicable.",[11,4249,4251],{"id":4250},"_7-rollback-and-recovery-notes","7) Rollback and recovery notes",[16,4253,4254],{},"If the issue gets worse, revert in this order:",[1173,4256,4257,4260,4263],{},[66,4258,4259],{},"proxy config changes",[66,4261,4262],{},"recent Django security setting changes",[66,4264,4265],{},"full app deployment to the previous known-good release",[16,4267,4268],{},"Before restoring proxy changes, validate the previous config file is still available and passes a syntax check:",[106,4270,4272],{"className":108,"code":4271,"language":110,"meta":111,"style":111},"sudo nginx -t\n",[20,4273,4274],{"__ignoreMap":111},[115,4275,4276,4278,4280],{"class":117,"line":118},[115,4277,2001],{"class":262},[115,4279,3906],{"class":132},[115,4281,4282],{"class":202}," -t\n",[16,4284,4285],{},"If you version your infrastructure config, restore the last known-good Nginx or Caddy revision rather than editing from memory.",[16,4287,4288],{},"Do not disable CSRF protection to get around a 403. That removes an important production security control and usually hides the real deployment problem.",[16,4290,4291],{},"Keep a known-good copy of:",[63,4293,4294,4297,4300],{},[66,4295,4296],{},"Django production settings",[66,4298,4299],{},"Nginx or Caddy config",[66,4301,4302],{},"environment variable values used for host and security settings",[16,4304,4305],{},"After rollback, re-run verification:",[63,4307,4308,4310,4313],{},[66,4309,1137],{},[66,4311,4312],{},"normal form POST works",[66,4314,4315],{},"no repeated CSRF warnings in logs",[52,4317,4319],{"id":4318},"when-to-script-this","When to script this",[16,4321,4322,4323,1153,4325,4327],{},"If you deploy multiple Django apps or environments, this is a good candidate for automation. A reusable settings template, proxy template, and post-deploy smoke test can check ",[20,4324,2719],{},[20,4326,2725],{},", forwarded headers, and whether a form page returns a CSRF cookie. That reduces repeated manual mistakes without changing the core security model.",[11,4329,1321],{"id":1320},[16,4331,4332],{},"Django CSRF protection validates several signals together:",[63,4334,4335,4338,4341],{},[66,4336,4337],{},"the CSRF cookie",[66,4339,4340],{},"the submitted token",[66,4342,4343],{},"the request origin or referer for secure requests",[16,4345,4346],{},"That is why production-only failures often point to deployment infrastructure, not just template code.",[16,4348,4349,4350,4352],{},"Reverse proxies and TLS termination are common root causes because Django may see an internal HTTP request unless the proxy forwards ",[20,4351,3203],{}," and Django is configured to trust it. When that happens, referer and origin validation can fail even though the browser is using HTTPS correctly.",[16,4354,4355,4357,4358,4360],{},[20,4356,2725],{}," is also commonly misunderstood. It does not replace ",[20,4359,2719],{},", and it must match the real browser-visible origin, including scheme.",[11,4362,1337],{"id":1336},[63,4364,4365,4378,4384,4393,4399,4405],{},[66,4366,4367,4370,4371,3146,4374,4377],{},[1226,4368,4369],{},"Cross-subdomain apps:"," if forms move between ",[20,4372,4373],{},"app.example.com",[20,4375,4376],{},"admin.example.com",", review cookie domain and trusted origins carefully.",[66,4379,4380,4383],{},[1226,4381,4382],{},"Admin on a separate domain:"," include that exact HTTPS origin if admin POSTs happen there.",[66,4385,4386,4389,4390,4392],{},[1226,4387,4388],{},"Load balancers and platform proxies:"," confirm they preserve ",[20,4391,3648],{}," and forward protocol information correctly.",[66,4394,4395,4398],{},[1226,4396,4397],{},"Docker deployments:"," keep these settings environment-driven, but validate that environment values match the real production domain.",[66,4400,4401,4404],{},[1226,4402,4403],{},"CDN or WAF interference:"," some layers cache login pages, strip headers, or alter cookie behavior. Bypass them temporarily for testing if needed.",[66,4406,4407,4410],{},[1226,4408,4409],{},"Redirect chains:"," unexpected redirects during login or form POST can switch host or scheme and cause false CSRF failures.",[11,4412,1386],{"id":1385},[16,4414,4415,4416,211],{},"If you need the underlying model first, read ",[1395,4417,4419],{"href":4418},"\u002Ffix-issues\u002Ffix-csrf-verification-failed-django-production","How Django CSRF Protection Works in Production",[16,4421,4422,4423,3146,4426,211],{},"For deployment configuration details, see ",[1395,4424,4425],{"href":2985},"Deploy Django with Gunicorn and Nginx",[1395,4427,4429],{"href":4428},"\u002Fdeploy\u002Fconfigure-https-lets-encrypt-django","How to Configure HTTPS for Django Behind a Reverse Proxy",[16,4431,4432,4433,211],{},"For broader security debugging, see ",[1395,4434,4435],{"href":4418},"Django 403 Errors in Production: Common Causes and Fixes",[16,4437,4438],{},"You may also need:",[63,4440,4441,4447,4451],{},[66,4442,4443],{},[1395,4444,4446],{"href":4445},"\u002Ffix-issues\u002Ffix-disallowedhost-django-production","How to Fix DisallowedHost in Django Production",[66,4448,4449],{},[1395,4450,3000],{"href":2999},[66,4452,4453],{},[1395,4454,4456],{"href":4455},"\u002Ffix-issues\u002Fdebug-django-502-bad-gateway","Django 502 Bad Gateway: Causes and Fixes",[11,4458,1420],{"id":1419},[52,4460,4462],{"id":4461},"why-does-django-csrf-fail-only-in-production-and-not-locally","Why does Django CSRF fail only in production and not locally?",[16,4464,4465],{},"Local development often has no reverse proxy, no HTTPS termination, and simpler hostnames. Production adds real domains, redirects, secure cookies, and forwarded headers, which is where CSRF validation usually breaks.",[52,4467,4469,4470,3146,4472,4474],{"id":4468},"do-i-need-both-allowed_hosts-and-csrf_trusted_origins","Do I need both ",[20,4471,2719],{},[20,4473,2725],{},"?",[16,4476,4477,4478,4480,4481,4483],{},"Yes, when your deployment needs both. ",[20,4479,2719],{}," validates the incoming host header. ",[20,4482,2725],{}," tells Django which origins are trusted for unsafe requests such as POST.",[52,4485,4487],{"id":4486},"can-nginx-cause-csrf-verification-failures","Can Nginx cause CSRF verification failures?",[16,4489,4490,4491,4493,4494,4496],{},"Yes. If Nginx does not forward ",[20,4492,3648],{}," or ",[20,4495,3203],{}," correctly, Django can misread the request origin or scheme and reject valid POSTs.",[52,4498,4500],{"id":4499},"why-is-the-csrf-cookie-missing-in-production","Why is the CSRF cookie missing in production?",[16,4502,4503,4504,4507],{},"Common causes are incorrect cookie settings, cached form responses, missing ",[20,4505,4506],{},"{% csrf_token %}"," in rendered templates, or proxy, CDN, or WAF behavior that changes headers or cookies.",[52,4509,4511],{"id":4510},"should-i-ever-disable-csrf-protection-to-fix-a-403","Should I ever disable CSRF protection to fix a 403?",[16,4513,4514],{},"No. Fix the host, origin, proxy, or cookie configuration instead. Disabling CSRF protection is not a safe production fix.",[1485,4516,4517],{},"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 .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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":4519},[4520,4521,4522,4523,4524,4525,4526,4527,4528,4529,4532,4533,4534,4535],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":3241,"depth":136,"text":3242},{"id":3519,"depth":136,"text":3520},{"id":3676,"depth":136,"text":3677},{"id":3965,"depth":136,"text":3966},{"id":4080,"depth":136,"text":4081},{"id":4171,"depth":136,"text":4172},{"id":4250,"depth":136,"text":4251,"children":4530},[4531],{"id":4318,"depth":149,"text":4319},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":4536},[4537,4538,4540,4541,4542],{"id":4461,"depth":149,"text":4462},{"id":4468,"depth":149,"text":4539},"Do I need both ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS?",{"id":4486,"depth":149,"text":4487},{"id":4499,"depth":149,"text":4500},{"id":4510,"depth":149,"text":4511},"Troubleshooting","CSRF Verification Failed in Django production usually appears after a deployment change that affects domains, HTTPS, cookies, or proxy headers.","TOFU","informational",{},"\u002Ffix-csrf-verification-failed-django-production","4",[4445,4551,4455],"\u002Ffix-issues\u002Ffix-static-files-not-loading-django","fix","fix-issues",{"title":3108,"description":4544},[1557],"fix-csrf-verification-failed-django-production",[1557],"troubleshooting","MVVQDLgiPSvZsrDMKS0e9OS-ws4psECmmp2h7l4hmAk",{"id":4561,"title":4562,"body":4563,"category":4543,"description":6327,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":4546,"meta":6328,"navigation":309,"path":6329,"priority":6330,"related":6331,"role":4552,"section":4553,"seo":6334,"stack":6335,"stem":6337,"tags":6338,"type":4558,"__hash__":6339},"articles\u002Fdebug-celery-not-running-django-production.md","Celery Not Running in Django Production: Debugging Guide",{"type":8,"value":4564,"toc":6260},[4565,4567,4570,4573,4587,4590,4592,4595,4618,4620,4624,4627,4653,4656,4672,4675,4679,4682,4686,4688,4736,4740,4774,4777,4817,4821,4842,4845,4849,4947,4950,4953,4957,4960,5008,5012,5015,5019,5058,5062,5090,5094,5110,5115,5142,5145,5168,5175,5178,5181,5185,5188,5191,5218,5222,5242,5245,5285,5288,5291,5294,5329,5332,5336,5339,5343,5408,5415,5419,5436,5439,5459,5462,5465,5468,5472,5475,5478,5482,5531,5534,5541,5557,5560,5564,5579,5582,5588,5592,5595,5599,5694,5700,5742,5748,5751,5768,5771,5775,5778,5781,5801,5804,5807,5810,5813,5824,5828,5831,5858,5860,5871,5874,5877,5880,5884,5887,5889,5912,5915,5918,5921,6027,6031,6034,6037,6040,6043,6046,6050,6053,6057,6071,6106,6109,6112,6116,6119,6130,6133,6135,6138,6141,6144,6146,6173,6175,6182,6192,6198,6201,6220,6222,6226,6229,6233,6236,6240,6243,6247,6250,6254,6257],[11,4566,14],{"id":13},[16,4568,4569],{},"A common production failure is that the Django web app is healthy, but Celery tasks stop running. You may see emails not sending, background jobs piling up, periodic tasks never firing, or tasks queued in Redis but never consumed.",[16,4571,4572],{},"In production, this usually comes down to one of four things:",[1173,4574,4575,4578,4581,4584],{},[66,4576,4577],{},"the Celery worker is not running",[66,4579,4580],{},"Celery Beat is not running",[66,4582,4583],{},"the broker connection is broken",[66,4585,4586],{},"tasks are being published, but the worker is not consuming the expected queue or is failing after receipt",[16,4588,4589],{},"The goal is to identify which layer is failing before changing configuration.",[11,4591,30],{"id":29},[16,4593,4594],{},"To debug Celery not running in Django production, check these in order:",[1173,4596,4597,4600,4603,4606,4609,4612,4615],{},[66,4598,4599],{},"confirm whether the failure is worker, beat, broker, or task code",[66,4601,4602],{},"check service status and logs",[66,4604,4605],{},"verify the worker is using the correct Django settings and environment variables",[66,4607,4608],{},"test broker connectivity from the worker host",[66,4610,4611],{},"confirm tasks are registered and the worker is listening to the right queue",[66,4613,4614],{},"if periodic tasks fail, make sure Beat runs as a separate production service",[66,4616,4617],{},"after any fix, enqueue a known test task and verify it executes exactly once",[11,4619,43],{"id":42},[11,4621,4623],{"id":4622},"_1-confirm-what-is-actually-failing","1. Confirm what is actually failing",[16,4625,4626],{},"Before restarting services, identify the failure mode.",[63,4628,4629,4635,4641,4647],{},[66,4630,4631,4634],{},[1226,4632,4633],{},"Worker failure",": queued tasks never run",[66,4636,4637,4640],{},[1226,4638,4639],{},"Beat failure",": scheduled tasks never get queued",[66,4642,4643,4646],{},[1226,4644,4645],{},"Broker failure",": workers cannot connect to Redis or RabbitMQ",[66,4648,4649,4652],{},[1226,4650,4651],{},"Task code failure",": worker receives tasks, but they crash",[16,4654,4655],{},"Also record your process model:",[63,4657,4658,4662,4667,4670],{},[66,4659,4660],{},[20,4661,1277],{},[66,4663,4664],{},[20,4665,4666],{},"supervisord",[66,4668,4669],{},"Docker Compose",[66,4671,1675],{},[16,4673,4674],{},"That determines where logs and restart behavior live.",[52,4676,4678],{"id":4677},"verification","Verification",[16,4680,4681],{},"Check whether tasks are being queued at all. If you use Redis as a broker, queue growth can indicate publish succeeds but consume fails. If nothing is queued, the issue may be in Django task submission or Beat.",[11,4683,4685],{"id":4684},"_2-check-whether-celery-processes-are-running","2. Check whether Celery processes are running",[52,4687,1277],{"id":1277},[106,4689,4691],{"className":108,"code":4690,"language":110,"meta":111,"style":111},"sudo systemctl status celery\nsudo systemctl status celery-beat\nsudo systemctl is-enabled celery\nsudo systemctl is-enabled celery-beat\n",[20,4692,4693,4704,4715,4726],{"__ignoreMap":111},[115,4694,4695,4697,4699,4701],{"class":117,"line":118},[115,4696,2001],{"class":262},[115,4698,3480],{"class":132},[115,4700,1984],{"class":132},[115,4702,4703],{"class":132}," celery\n",[115,4705,4706,4708,4710,4712],{"class":117,"line":136},[115,4707,2001],{"class":262},[115,4709,3480],{"class":132},[115,4711,1984],{"class":132},[115,4713,4714],{"class":132}," celery-beat\n",[115,4716,4717,4719,4721,4724],{"class":117,"line":149},[115,4718,2001],{"class":262},[115,4720,3480],{"class":132},[115,4722,4723],{"class":132}," is-enabled",[115,4725,4703],{"class":132},[115,4727,4728,4730,4732,4734],{"class":117,"line":162},[115,4729,2001],{"class":262},[115,4731,3480],{"class":132},[115,4733,4723],{"class":132},[115,4735,4714],{"class":132},[52,4737,4739],{"id":4738},"supervisor","Supervisor",[106,4741,4743],{"className":108,"code":4742,"language":110,"meta":111,"style":111},"sudo supervisorctl status\nsudo supervisorctl restart celery\nsudo supervisorctl restart celery-beat\n",[20,4744,4745,4754,4764],{"__ignoreMap":111},[115,4746,4747,4749,4752],{"class":117,"line":118},[115,4748,2001],{"class":262},[115,4750,4751],{"class":132}," supervisorctl",[115,4753,2017],{"class":132},[115,4755,4756,4758,4760,4762],{"class":117,"line":136},[115,4757,2001],{"class":262},[115,4759,4751],{"class":132},[115,4761,3483],{"class":132},[115,4763,4703],{"class":132},[115,4765,4766,4768,4770,4772],{"class":117,"line":149},[115,4767,2001],{"class":262},[115,4769,4751],{"class":132},[115,4771,3483],{"class":132},[115,4773,4714],{"class":132},[52,4775,4669],{"id":4776},"docker-compose",[106,4778,4780],{"className":108,"code":4779,"language":110,"meta":111,"style":111},"docker compose ps\ndocker compose logs worker --tail=100\ndocker compose logs beat --tail=100\n",[20,4781,4782,4791,4804],{"__ignoreMap":111},[115,4783,4784,4786,4788],{"class":117,"line":118},[115,4785,3295],{"class":262},[115,4787,3298],{"class":132},[115,4789,4790],{"class":132}," ps\n",[115,4792,4793,4795,4797,4799,4802],{"class":117,"line":136},[115,4794,3295],{"class":262},[115,4796,3298],{"class":132},[115,4798,3301],{"class":132},[115,4800,4801],{"class":132}," worker",[115,4803,3307],{"class":202},[115,4805,4806,4808,4810,4812,4815],{"class":117,"line":149},[115,4807,3295],{"class":262},[115,4809,3298],{"class":132},[115,4811,3301],{"class":132},[115,4813,4814],{"class":132}," beat",[115,4816,3307],{"class":202},[52,4818,4820],{"id":4819},"direct-process-check","Direct process check",[106,4822,4824],{"className":108,"code":4823,"language":110,"meta":111,"style":111},"ps aux | grep '[c]elery'\n",[20,4825,4826],{"__ignoreMap":111},[115,4827,4828,4831,4834,4836,4839],{"class":117,"line":118},[115,4829,4830],{"class":262},"ps",[115,4832,4833],{"class":132}," aux",[115,4835,579],{"class":121},[115,4837,4838],{"class":262}," grep",[115,4840,4841],{"class":132}," '[c]elery'\n",[16,4843,4844],{},"If the service is running, confirm it uses the right user, working directory, and virtualenv.",[52,4846,4848],{"id":4847},"example-systemd-unit","Example systemd unit",[106,4850,4852],{"className":2026,"code":4851,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Celery Worker\nAfter=network.target\n\n[Service]\nType=simple\nUser=django\nGroup=django\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A myapp worker --loglevel=INFO\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n",[20,4853,4854,4858,4865,4871,4875,4879,4887,4893,4899,4906,4913,4926,4933,4937,4941],{"__ignoreMap":111},[115,4855,4856],{"class":117,"line":118},[115,4857,2035],{"class":262},[115,4859,4860,4862],{"class":117,"line":136},[115,4861,2040],{"class":121},[115,4863,4864],{"class":125},"=Celery Worker\n",[115,4866,4867,4869],{"class":117,"line":149},[115,4868,2048],{"class":121},[115,4870,2051],{"class":125},[115,4872,4873],{"class":117,"line":162},[115,4874,310],{"emptyLinePlaceholder":309},[115,4876,4877],{"class":117,"line":175},[115,4878,2060],{"class":262},[115,4880,4881,4884],{"class":117,"line":350},[115,4882,4883],{"class":121},"Type",[115,4885,4886],{"class":125},"=simple\n",[115,4888,4889,4891],{"class":117,"line":365},[115,4890,2065],{"class":121},[115,4892,2068],{"class":125},[115,4894,4895,4897],{"class":117,"line":380},[115,4896,2073],{"class":121},[115,4898,2068],{"class":125},[115,4900,4901,4903],{"class":117,"line":487},[115,4902,2081],{"class":121},[115,4904,4905],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fcurrent\n",[115,4907,4908,4910],{"class":117,"line":2095},[115,4909,2089],{"class":121},[115,4911,4912],{"class":125},"=\u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[115,4914,4915,4917,4920,4923],{"class":117,"line":2104},[115,4916,2107],{"class":121},[115,4918,4919],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A myapp worker --",[115,4921,4922],{"class":121},"loglevel",[115,4924,4925],{"class":125},"=INFO\n",[115,4927,4928,4930],{"class":117,"line":2113},[115,4929,2116],{"class":121},[115,4931,4932],{"class":125},"=always\n",[115,4934,4935],{"class":117,"line":2122},[115,4936,310],{"emptyLinePlaceholder":309},[115,4938,4939],{"class":117,"line":2131},[115,4940,2139],{"class":262},[115,4942,4943,4945],{"class":117,"line":2136},[115,4944,2145],{"class":121},[115,4946,2148],{"class":125},[52,4948,4678],{"id":4949},"verification-1",[16,4951,4952],{},"If the service exits immediately, do not keep restarting it blindly. Go to logs first.",[52,4954,4956],{"id":4955},"rollback-note","Rollback note",[16,4958,4959],{},"If a recent unit file change broke startup, restore the last known good unit or remove the bad override, then validate what systemd is actually loading:",[106,4961,4963],{"className":108,"code":4962,"language":110,"meta":111,"style":111},"sudo systemctl cat celery\nsudo systemctl daemon-reload\nsudo systemctl restart celery\nsudo systemctl status celery --no-pager\n",[20,4964,4965,4976,4985,4995],{"__ignoreMap":111},[115,4966,4967,4969,4971,4974],{"class":117,"line":118},[115,4968,2001],{"class":262},[115,4970,3480],{"class":132},[115,4972,4973],{"class":132}," cat",[115,4975,4703],{"class":132},[115,4977,4978,4980,4982],{"class":117,"line":136},[115,4979,2001],{"class":262},[115,4981,3480],{"class":132},[115,4983,4984],{"class":132}," daemon-reload\n",[115,4986,4987,4989,4991,4993],{"class":117,"line":149},[115,4988,2001],{"class":262},[115,4990,3480],{"class":132},[115,4992,3483],{"class":132},[115,4994,4703],{"class":132},[115,4996,4997,4999,5001,5003,5006],{"class":117,"line":162},[115,4998,2001],{"class":262},[115,5000,3480],{"class":132},[115,5002,1984],{"class":132},[115,5004,5005],{"class":132}," celery",[115,5007,2800],{"class":202},[11,5009,5011],{"id":5010},"_3-review-logs-first","3. Review logs first",[16,5013,5014],{},"For worker startup failures, logs usually show the cause quickly.",[52,5016,5018],{"id":5017},"systemd-journal","systemd journal",[106,5020,5022],{"className":108,"code":5021,"language":110,"meta":111,"style":111},"sudo journalctl -u celery -n 100 --no-pager\nsudo journalctl -u celery-beat -n 100 --no-pager\n",[20,5023,5024,5041],{"__ignoreMap":111},[115,5025,5026,5028,5031,5033,5035,5037,5039],{"class":117,"line":118},[115,5027,2001],{"class":262},[115,5029,5030],{"class":132}," journalctl",[115,5032,2788],{"class":202},[115,5034,5005],{"class":132},[115,5036,2794],{"class":202},[115,5038,2797],{"class":202},[115,5040,2800],{"class":202},[115,5042,5043,5045,5047,5049,5052,5054,5056],{"class":117,"line":136},[115,5044,2001],{"class":262},[115,5046,5030],{"class":132},[115,5048,2788],{"class":202},[115,5050,5051],{"class":132}," celery-beat",[115,5053,2794],{"class":202},[115,5055,2797],{"class":202},[115,5057,2800],{"class":202},[52,5059,5061],{"id":5060},"common-failures-to-look-for","Common failures to look for",[63,5063,5064,5069,5075,5078,5081,5084,5087],{},[66,5065,5066],{},[20,5067,5068],{},"ModuleNotFoundError",[66,5070,5071,5072],{},"bad ",[20,5073,5074],{},"DJANGO_SETTINGS_MODULE",[66,5076,5077],{},"permission denied on project directory or socket\u002Ffile paths",[66,5079,5080],{},"Redis authentication failure",[66,5082,5083],{},"RabbitMQ access refused",[66,5085,5086],{},"queue declaration errors",[66,5088,5089],{},"task import errors during startup",[52,5091,5093],{"id":5092},"increase-log-verbosity-temporarily","Increase log verbosity temporarily",[106,5095,5097],{"className":108,"code":5096,"language":110,"meta":111,"style":111},"sudo systemctl edit celery\n",[20,5098,5099],{"__ignoreMap":111},[115,5100,5101,5103,5105,5108],{"class":117,"line":118},[115,5102,2001],{"class":262},[115,5104,3480],{"class":132},[115,5106,5107],{"class":132}," edit",[115,5109,4703],{"class":132},[16,5111,5112,5113,1642],{},"Override ",[20,5114,2107],{},[106,5116,5118],{"className":2026,"code":5117,"language":2028,"meta":111,"style":111},"[Service]\nExecStart=\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A myapp worker --loglevel=DEBUG\n",[20,5119,5120,5124,5131],{"__ignoreMap":111},[115,5121,5122],{"class":117,"line":118},[115,5123,2060],{"class":262},[115,5125,5126,5128],{"class":117,"line":136},[115,5127,2107],{"class":121},[115,5129,5130],{"class":125},"=\n",[115,5132,5133,5135,5137,5139],{"class":117,"line":149},[115,5134,2107],{"class":121},[115,5136,4919],{"class":125},[115,5138,4922],{"class":121},[115,5140,5141],{"class":125},"=DEBUG\n",[16,5143,5144],{},"Then:",[106,5146,5148],{"className":108,"code":5147,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl restart celery\n",[20,5149,5150,5158],{"__ignoreMap":111},[115,5151,5152,5154,5156],{"class":117,"line":118},[115,5153,2001],{"class":262},[115,5155,3480],{"class":132},[115,5157,4984],{"class":132},[115,5159,5160,5162,5164,5166],{"class":117,"line":136},[115,5161,2001],{"class":262},[115,5163,3480],{"class":132},[115,5165,3483],{"class":132},[115,5167,4703],{"class":132},[16,5169,5170,5171,5174],{},"Use this only long enough to diagnose. Return to ",[20,5172,5173],{},"INFO"," after the issue is clear.",[52,5176,4678],{"id":5177},"verification-2",[16,5179,5180],{},"You want to see worker startup complete and queues listed without repeated connection retries.",[11,5182,5184],{"id":5183},"_4-verify-django-settings-and-environment-variables","4. Verify Django settings and environment variables",[16,5186,5187],{},"A very common reason a Celery worker does not run in production is that the web process and worker process are not using the same environment.",[16,5189,5190],{},"Check the worker environment for:",[63,5192,5193,5197,5202,5207,5212,5215],{},[66,5194,5195],{},[20,5196,5074],{},[66,5198,5199],{},[20,5200,5201],{},"CELERY_BROKER_URL",[66,5203,5204],{},[20,5205,5206],{},"CELERY_RESULT_BACKEND",[66,5208,5209,5211],{},[20,5210,2713],{}," only if your Django settings import path requires it; Celery itself does not normally need it directly",[66,5213,5214],{},"database credentials if tasks use the database",[66,5216,5217],{},"timezone settings if Beat is involved",[52,5219,5221],{"id":5220},"example-environment-file","Example environment file",[106,5223,5225],{"className":2329,"code":5224,"language":2331,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=myapp.settings.production\nCELERY_BROKER_URL=redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F0\nCELERY_RESULT_BACKEND=redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F1\n",[20,5226,5227,5232,5237],{"__ignoreMap":111},[115,5228,5229],{"class":117,"line":118},[115,5230,5231],{},"DJANGO_SETTINGS_MODULE=myapp.settings.production\n",[115,5233,5234],{"class":117,"line":136},[115,5235,5236],{},"CELERY_BROKER_URL=redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F0\n",[115,5238,5239],{"class":117,"line":149},[115,5240,5241],{},"CELERY_RESULT_BACKEND=redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F1\n",[16,5243,5244],{},"For Docker:",[106,5246,5248],{"className":108,"code":5247,"language":110,"meta":111,"style":111},"docker compose exec worker env | sort\ndocker compose exec beat env | sort\n",[20,5249,5250,5269],{"__ignoreMap":111},[115,5251,5252,5254,5256,5259,5261,5264,5266],{"class":117,"line":118},[115,5253,3295],{"class":262},[115,5255,3298],{"class":132},[115,5257,5258],{"class":132}," exec",[115,5260,4801],{"class":132},[115,5262,5263],{"class":132}," env",[115,5265,579],{"class":121},[115,5267,5268],{"class":262}," sort\n",[115,5270,5271,5273,5275,5277,5279,5281,5283],{"class":117,"line":136},[115,5272,3295],{"class":262},[115,5274,3298],{"class":132},[115,5276,5258],{"class":132},[115,5278,4814],{"class":132},[115,5280,5263],{"class":132},[115,5282,579],{"class":121},[115,5284,5268],{"class":262},[16,5286,5287],{},"Do not print secrets into shared logs or tickets.",[52,5289,4678],{"id":5290},"verification-3",[16,5292,5293],{},"From the same virtualenv and same environment as the worker, run:",[106,5295,5297],{"className":108,"code":5296,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\ncelery -A myapp report\n",[20,5298,5299,5307,5315],{"__ignoreMap":111},[115,5300,5301,5304],{"class":117,"line":118},[115,5302,5303],{"class":202},"cd",[115,5305,5306],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[115,5308,5309,5312],{"class":117,"line":136},[115,5310,5311],{"class":202},"source",[115,5313,5314],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\n",[115,5316,5317,5320,5323,5326],{"class":117,"line":149},[115,5318,5319],{"class":262},"celery",[115,5321,5322],{"class":202}," -A",[115,5324,5325],{"class":132}," myapp",[115,5327,5328],{"class":132}," report\n",[16,5330,5331],{},"This helps confirm the app loads and the broker configuration is what you expect.",[11,5333,5335],{"id":5334},"_5-confirm-broker-connectivity-and-health","5. Confirm broker connectivity and health",[16,5337,5338],{},"If broker connectivity is the cause, the worker may be healthy but unable to connect.",[52,5340,5342],{"id":5341},"redis-checks","Redis checks",[106,5344,5346],{"className":108,"code":5345,"language":110,"meta":111,"style":111},"nc -zv redis.internal 6379\n\n# If Redis does not require auth\u002FTLS\nredis-cli -h redis.internal -p 6379 ping\n\n# If Redis requires auth\nredis-cli -u 'redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F0' ping\n",[20,5347,5348,5362,5366,5371,5388,5392,5397],{"__ignoreMap":111},[115,5349,5350,5353,5356,5359],{"class":117,"line":118},[115,5351,5352],{"class":262},"nc",[115,5354,5355],{"class":202}," -zv",[115,5357,5358],{"class":132}," redis.internal",[115,5360,5361],{"class":202}," 6379\n",[115,5363,5364],{"class":117,"line":136},[115,5365,310],{"emptyLinePlaceholder":309},[115,5367,5368],{"class":117,"line":149},[115,5369,5370],{"class":3861},"# If Redis does not require auth\u002FTLS\n",[115,5372,5373,5376,5378,5380,5382,5385],{"class":117,"line":162},[115,5374,5375],{"class":262},"redis-cli",[115,5377,992],{"class":202},[115,5379,5358],{"class":132},[115,5381,1001],{"class":202},[115,5383,5384],{"class":202}," 6379",[115,5386,5387],{"class":132}," ping\n",[115,5389,5390],{"class":117,"line":175},[115,5391,310],{"emptyLinePlaceholder":309},[115,5393,5394],{"class":117,"line":350},[115,5395,5396],{"class":3861},"# If Redis requires auth\n",[115,5398,5399,5401,5403,5406],{"class":117,"line":365},[115,5400,5375],{"class":262},[115,5402,2788],{"class":202},[115,5404,5405],{"class":132}," 'redis:\u002F\u002F:strongpassword@redis.internal:6379\u002F0'",[115,5407,5387],{"class":132},[16,5409,5410,5411,5414],{},"If Redis requires authentication or TLS, test with the same connection settings the worker uses; a plain ",[20,5412,5413],{},"redis-cli ... ping"," can be misleading.",[52,5416,5418],{"id":5417},"rabbitmq-or-generic-port-reachability","RabbitMQ or generic port reachability",[106,5420,5422],{"className":108,"code":5421,"language":110,"meta":111,"style":111},"nc -zv broker.internal 5672\n",[20,5423,5424],{"__ignoreMap":111},[115,5425,5426,5428,5430,5433],{"class":117,"line":118},[115,5427,5352],{"class":262},[115,5429,5355],{"class":202},[115,5431,5432],{"class":132}," broker.internal",[115,5434,5435],{"class":202}," 5672\n",[16,5437,5438],{},"Check:",[63,5440,5441,5444,5447,5450,5453,5456],{},[66,5442,5443],{},"hostname correctness",[66,5445,5446],{},"port",[66,5448,5449],{},"credentials",[66,5451,5452],{},"TLS requirements",[66,5454,5455],{},"firewall rules",[66,5457,5458],{},"DNS resolution on the worker host",[16,5460,5461],{},"If the broker restarted recently, investigate whether in-memory queues were lost or whether persistence is configured correctly.",[52,5463,4678],{"id":5464},"verification-4",[16,5466,5467],{},"Worker logs should stop showing reconnect loops and start showing ready or connected state.",[11,5469,5471],{"id":5470},"_6-make-sure-the-worker-is-consuming-the-right-queue","6. Make sure the worker is consuming the right queue",[16,5473,5474],{},"A major reason tasks are queued but not running is queue mismatch.",[16,5476,5477],{},"If you route tasks to a custom queue, but start the worker with a restrictive queue list, tasks will sit forever.",[52,5479,5481],{"id":5480},"check-worker-queue-settings","Check worker queue settings",[106,5483,5485],{"className":108,"code":5484,"language":110,"meta":111,"style":111},"# Run from the same virtualenv\u002Fenvironment as the worker\ncelery -A myapp inspect ping\ncelery -A myapp inspect active_queues\ncelery -A myapp inspect registered\n",[20,5486,5487,5492,5505,5518],{"__ignoreMap":111},[115,5488,5489],{"class":117,"line":118},[115,5490,5491],{"class":3861},"# Run from the same virtualenv\u002Fenvironment as the worker\n",[115,5493,5494,5496,5498,5500,5503],{"class":117,"line":136},[115,5495,5319],{"class":262},[115,5497,5322],{"class":202},[115,5499,5325],{"class":132},[115,5501,5502],{"class":132}," inspect",[115,5504,5387],{"class":132},[115,5506,5507,5509,5511,5513,5515],{"class":117,"line":149},[115,5508,5319],{"class":262},[115,5510,5322],{"class":202},[115,5512,5325],{"class":132},[115,5514,5502],{"class":132},[115,5516,5517],{"class":132}," active_queues\n",[115,5519,5520,5522,5524,5526,5528],{"class":117,"line":162},[115,5521,5319],{"class":262},[115,5523,5322],{"class":202},[115,5525,5325],{"class":132},[115,5527,5502],{"class":132},[115,5529,5530],{"class":132}," registered\n",[16,5532,5533],{},"If these commands return no replies, confirm the worker is running and that remote control is not disabled before concluding the queues or task registry are wrong.",[16,5535,5536,5537,5540],{},"If the worker is started with ",[20,5538,5539],{},"-Q",", verify it includes the queue being used:",[106,5542,5544],{"className":2026,"code":5543,"language":2028,"meta":111,"style":111},"ExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A myapp worker -Q default,emails --loglevel=INFO\n",[20,5545,5546],{"__ignoreMap":111},[115,5547,5548,5550,5553,5555],{"class":117,"line":118},[115,5549,2107],{"class":121},[115,5551,5552],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A myapp worker -Q default,emails --",[115,5554,4922],{"class":121},[115,5556,4925],{"class":125},[16,5558,5559],{},"If no queue is explicitly set in task routing, confirm your default queue configuration is consistent.",[52,5561,5563],{"id":5562},"example-django-settings","Example Django settings",[106,5565,5567],{"className":2369,"code":5566,"language":1114,"meta":111,"style":111},"CELERY_TASK_DEFAULT_QUEUE = \"default\"\n",[20,5568,5569],{"__ignoreMap":111},[115,5570,5571,5574,5576],{"class":117,"line":118},[115,5572,5573],{"class":202},"CELERY_TASK_DEFAULT_QUEUE",[115,5575,2380],{"class":121},[115,5577,5578],{"class":132}," \"default\"\n",[52,5580,4678],{"id":5581},"verification-5",[16,5583,5584,5585,211],{},"The task should appear on a queue the worker actually lists in ",[20,5586,5587],{},"active_queues",[11,5589,5591],{"id":5590},"_7-confirm-task-discovery-and-registration","7. Confirm task discovery and registration",[16,5593,5594],{},"If workers start but do not know about your tasks, they cannot execute them.",[52,5596,5598],{"id":5597},"typical-celery-app-setup","Typical Celery app setup",[106,5600,5602],{"className":2369,"code":5601,"language":1114,"meta":111,"style":111},"# myapp\u002Fcelery.py\nimport os\nfrom celery import Celery\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"myapp.settings.production\")\n\napp = Celery(\"myapp\")\napp.config_from_object(\"django.conf:settings\", namespace=\"CELERY\")\napp.autodiscover_tasks()\n",[20,5603,5604,5609,5617,5630,5634,5649,5653,5668,5689],{"__ignoreMap":111},[115,5605,5606],{"class":117,"line":118},[115,5607,5608],{"class":3861},"# myapp\u002Fcelery.py\n",[115,5610,5611,5614],{"class":117,"line":136},[115,5612,5613],{"class":121},"import",[115,5615,5616],{"class":125}," os\n",[115,5618,5619,5622,5625,5627],{"class":117,"line":149},[115,5620,5621],{"class":121},"from",[115,5623,5624],{"class":125}," celery ",[115,5626,5613],{"class":121},[115,5628,5629],{"class":125}," Celery\n",[115,5631,5632],{"class":117,"line":162},[115,5633,310],{"emptyLinePlaceholder":309},[115,5635,5636,5639,5642,5644,5647],{"class":117,"line":175},[115,5637,5638],{"class":125},"os.environ.setdefault(",[115,5640,5641],{"class":132},"\"DJANGO_SETTINGS_MODULE\"",[115,5643,1153],{"class":125},[115,5645,5646],{"class":132},"\"myapp.settings.production\"",[115,5648,2394],{"class":125},[115,5650,5651],{"class":117,"line":350},[115,5652,310],{"emptyLinePlaceholder":309},[115,5654,5655,5658,5660,5663,5666],{"class":117,"line":365},[115,5656,5657],{"class":125},"app ",[115,5659,129],{"class":121},[115,5661,5662],{"class":125}," Celery(",[115,5664,5665],{"class":132},"\"myapp\"",[115,5667,2394],{"class":125},[115,5669,5670,5673,5676,5678,5682,5684,5687],{"class":117,"line":380},[115,5671,5672],{"class":125},"app.config_from_object(",[115,5674,5675],{"class":132},"\"django.conf:settings\"",[115,5677,1153],{"class":125},[115,5679,5681],{"class":5680},"s4XuR","namespace",[115,5683,129],{"class":121},[115,5685,5686],{"class":132},"\"CELERY\"",[115,5688,2394],{"class":125},[115,5690,5691],{"class":117,"line":487},[115,5692,5693],{"class":125},"app.autodiscover_tasks()\n",[16,5695,5696,5697,241],{},"And in ",[20,5698,5699],{},"myapp\u002F__init__.py",[106,5701,5703],{"className":2369,"code":5702,"language":1114,"meta":111,"style":111},"from .celery import app as celery_app\n\n__all__ = (\"celery_app\",)\n",[20,5704,5705,5723,5727],{"__ignoreMap":111},[115,5706,5707,5709,5712,5714,5717,5720],{"class":117,"line":118},[115,5708,5621],{"class":121},[115,5710,5711],{"class":125}," .celery ",[115,5713,5613],{"class":121},[115,5715,5716],{"class":125}," app ",[115,5718,5719],{"class":121},"as",[115,5721,5722],{"class":125}," celery_app\n",[115,5724,5725],{"class":117,"line":136},[115,5726,310],{"emptyLinePlaceholder":309},[115,5728,5729,5732,5734,5736,5739],{"class":117,"line":149},[115,5730,5731],{"class":202},"__all__",[115,5733,2380],{"class":121},[115,5735,2383],{"class":125},[115,5737,5738],{"class":132},"\"celery_app\"",[115,5740,5741],{"class":125},",)\n",[16,5743,5744,5745,211],{},"Check that task modules exist under installed Django apps, usually as ",[20,5746,5747],{},"tasks.py",[52,5749,4678],{"id":5750},"verification-6",[106,5752,5754],{"className":108,"code":5753,"language":110,"meta":111,"style":111},"celery -A myapp inspect registered\n",[20,5755,5756],{"__ignoreMap":111},[115,5757,5758,5760,5762,5764,5766],{"class":117,"line":118},[115,5759,5319],{"class":262},[115,5761,5322],{"class":202},[115,5763,5325],{"class":132},[115,5765,5502],{"class":132},[115,5767,5530],{"class":132},[16,5769,5770],{},"If expected tasks are missing, inspect startup logs for import-time exceptions.",[11,5772,5774],{"id":5773},"_8-debug-failed-tasks-that-appear-to-be-running","8. Debug failed tasks that appear to be running",[16,5776,5777],{},"Sometimes tasks are received and then fail immediately.",[16,5779,5780],{},"Look for tracebacks involving:",[63,5782,5783,5786,5789,5792,5795,5798],{},[66,5784,5785],{},"database connectivity",[66,5787,5788],{},"missing migrations",[66,5790,5791],{},"SMTP credentials",[66,5793,5794],{},"cache backends",[66,5796,5797],{},"third-party APIs",[66,5799,5800],{},"serialization errors",[16,5802,5803],{},"If the task arguments are not serializable by your configured serializer, the task may never run correctly.",[16,5805,5806],{},"Also review retry behavior. A task that retries forever can look like a stuck system rather than an obvious failure.",[52,5808,4678],{"id":5809},"verification-7",[16,5811,5812],{},"Find a log sequence showing:",[1173,5814,5815,5818,5821],{},[66,5816,5817],{},"task received",[66,5819,5820],{},"task started",[66,5822,5823],{},"task succeeded or failed with traceback",[11,5825,5827],{"id":5826},"_9-if-celery-beat-is-not-running","9. If Celery Beat is not running",[16,5829,5830],{},"For periodic task failures, remember Beat should usually run as its own process.",[106,5832,5834],{"className":108,"code":5833,"language":110,"meta":111,"style":111},"sudo systemctl status celery-beat\ndocker compose logs beat --tail=100\n",[20,5835,5836,5846],{"__ignoreMap":111},[115,5837,5838,5840,5842,5844],{"class":117,"line":118},[115,5839,2001],{"class":262},[115,5841,3480],{"class":132},[115,5843,1984],{"class":132},[115,5845,4714],{"class":132},[115,5847,5848,5850,5852,5854,5856],{"class":117,"line":136},[115,5849,3295],{"class":262},[115,5851,3298],{"class":132},[115,5853,3301],{"class":132},[115,5855,4814],{"class":132},[115,5857,3307],{"class":202},[16,5859,5438],{},[63,5861,5862,5865,5868],{},[66,5863,5864],{},"schedule backend configuration",[66,5866,5867],{},"timezone consistency",[66,5869,5870],{},"only one Beat instance runs in multi-server production unless you intentionally use a clustered scheduler",[16,5872,5873],{},"If multiple Beat instances run against the same schedule, periodic tasks may duplicate.",[52,5875,4678],{"id":5876},"verification-8",[16,5878,5879],{},"You should see Beat emitting scheduled tasks at the expected times, and workers receiving them.",[11,5881,5883],{"id":5882},"_10-deployment-specific-fixes","10. Deployment-specific fixes",[52,5885,1277],{"id":5886},"systemd-1",[16,5888,5438],{},[63,5890,5891,5895,5899,5903,5907],{},[66,5892,5893],{},[20,5894,2107],{},[66,5896,5897],{},[20,5898,2081],{},[66,5900,5901],{},[20,5902,2065],{},[66,5904,5905],{},[20,5906,2089],{},[66,5908,5909],{},[20,5910,5911],{},"Restart=always",[16,5913,5914],{},"Repeated crash loops can hide the real startup error. Inspect logs before relying on automatic restarts.",[52,5916,4669],{"id":5917},"docker-compose-1",[16,5919,5920],{},"Make sure worker and beat both receive the same app environment.",[106,5922,5924],{"className":2485,"code":5923,"language":2487,"meta":111,"style":111},"services:\n  worker:\n    image: myapp:latest\n    command: celery -A myapp worker --loglevel=INFO\n    restart: always\n    env_file:\n      - .env\n\n  beat:\n    image: myapp:latest\n    command: celery -A myapp beat --loglevel=INFO\n    restart: always\n    env_file:\n      - .env\n",[20,5925,5926,5932,5938,5947,5956,5966,5972,5979,5983,5990,5998,6007,6015,6021],{"__ignoreMap":111},[115,5927,5928,5930],{"class":117,"line":118},[115,5929,2495],{"class":2494},[115,5931,2498],{"class":125},[115,5933,5934,5936],{"class":117,"line":136},[115,5935,2561],{"class":2494},[115,5937,2498],{"class":125},[115,5939,5940,5942,5944],{"class":117,"line":149},[115,5941,2510],{"class":2494},[115,5943,2513],{"class":125},[115,5945,5946],{"class":132},"myapp:latest\n",[115,5948,5949,5951,5953],{"class":117,"line":162},[115,5950,2576],{"class":2494},[115,5952,2513],{"class":125},[115,5954,5955],{"class":132},"celery -A myapp worker --loglevel=INFO\n",[115,5957,5958,5961,5963],{"class":117,"line":175},[115,5959,5960],{"class":2494},"    restart",[115,5962,2513],{"class":125},[115,5964,5965],{"class":132},"always\n",[115,5967,5968,5970],{"class":117,"line":350},[115,5969,2521],{"class":2494},[115,5971,2498],{"class":125},[115,5973,5974,5977],{"class":117,"line":365},[115,5975,5976],{"class":125},"      - ",[115,5978,2526],{"class":132},[115,5980,5981],{"class":117,"line":380},[115,5982,310],{"emptyLinePlaceholder":309},[115,5984,5985,5988],{"class":117,"line":487},[115,5986,5987],{"class":2494},"  beat",[115,5989,2498],{"class":125},[115,5991,5992,5994,5996],{"class":117,"line":2095},[115,5993,2510],{"class":2494},[115,5995,2513],{"class":125},[115,5997,5946],{"class":132},[115,5999,6000,6002,6004],{"class":117,"line":2104},[115,6001,2576],{"class":2494},[115,6003,2513],{"class":125},[115,6005,6006],{"class":132},"celery -A myapp beat --loglevel=INFO\n",[115,6008,6009,6011,6013],{"class":117,"line":2113},[115,6010,5960],{"class":2494},[115,6012,2513],{"class":125},[115,6014,5965],{"class":132},[115,6016,6017,6019],{"class":117,"line":2122},[115,6018,2521],{"class":2494},[115,6020,2498],{"class":125},[115,6022,6023,6025],{"class":117,"line":2131},[115,6024,5976],{"class":125},[115,6026,2526],{"class":132},[52,6028,6030],{"id":6029},"release-workflow","Release workflow",[16,6032,6033],{},"A common issue is deploying new Django code without restarting worker and beat. The web app serves new code, but Celery still runs old code.",[16,6035,6036],{},"After deploy, restart background services explicitly.",[16,6038,6039],{},"If a release includes schema changes, apply migrations before workers execute tasks that depend on the new schema. Restarting workers onto new code before migrations complete can break task execution.",[52,6041,4678],{"id":6042},"verification-9",[16,6044,6045],{},"After deployment, confirm service start time matches the new release time.",[11,6047,6049],{"id":6048},"_11-verify-the-fix-safely","11. Verify the fix safely",[16,6051,6052],{},"Enqueue a known task and check logs.",[52,6054,6056],{"id":6055},"example-from-django-shell","Example from Django shell",[106,6058,6060],{"className":108,"code":6059,"language":110,"meta":111,"style":111},"python manage.py shell\n",[20,6061,6062],{"__ignoreMap":111},[115,6063,6064,6066,6068],{"class":117,"line":118},[115,6065,1114],{"class":262},[115,6067,1117],{"class":132},[115,6069,6070],{"class":132}," shell\n",[106,6072,6074],{"className":2369,"code":6073,"language":1114,"meta":111,"style":111},"from myapp.tasks import healthcheck_task\nresult = healthcheck_task.delay()\nprint(result.id)\n",[20,6075,6076,6088,6098],{"__ignoreMap":111},[115,6077,6078,6080,6083,6085],{"class":117,"line":118},[115,6079,5621],{"class":121},[115,6081,6082],{"class":125}," myapp.tasks ",[115,6084,5613],{"class":121},[115,6086,6087],{"class":125}," healthcheck_task\n",[115,6089,6090,6093,6095],{"class":117,"line":136},[115,6091,6092],{"class":125},"result ",[115,6094,129],{"class":121},[115,6096,6097],{"class":125}," healthcheck_task.delay()\n",[115,6099,6100,6103],{"class":117,"line":149},[115,6101,6102],{"class":202},"print",[115,6104,6105],{"class":125},"(result.id)\n",[16,6107,6108],{},"Then check worker logs and expected side effect.",[16,6110,6111],{},"Also verify that queue depth decreases and the task runs once, not repeatedly.",[52,6113,6115],{"id":6114},"rollback-and-recovery","Rollback and recovery",[16,6117,6118],{},"If the new release broke Celery:",[1173,6120,6121,6124,6127],{},[66,6122,6123],{},"restore the last working app release or service file",[66,6125,6126],{},"restart worker and beat",[66,6128,6129],{},"inspect queued, retried, or duplicated tasks before replaying anything",[16,6131,6132],{},"Be careful with retries and idempotency. Re-running payment, email, webhook, or external API tasks can create duplicate side effects. If tasks may have been partially processed, treat replay as a recovery operation, not a routine restart step.",[11,6134,1321],{"id":1320},[16,6136,6137],{},"Celery production failures are usually not random. They happen at boundaries: process manager, environment loading, broker connectivity, task routing, or code import.",[16,6139,6140],{},"This workflow works because it narrows the problem in the right order: first determine whether tasks are queued, then whether workers are alive, then whether workers can connect, then whether they know the task and queue. That is faster and safer than changing multiple settings at once.",[16,6142,6143],{},"If your deployment repeatedly requires manual checks after every release, this is a good point to convert the process into reusable scripts or templates. The first parts worth automating are service restarts, broker connectivity checks, and a post-deploy smoke test that enqueues a known task and verifies completion.",[11,6145,1337],{"id":1336},[63,6147,6148,6151,6154,6157,6160,6163,6166],{},[66,6149,6150],{},"Long-running tasks may be killed by memory pressure or process limits rather than failing cleanly.",[66,6152,6153],{},"Tasks can fail after deployment if database migrations were not applied before workers started using new code.",[66,6155,6156],{},"Separate staging and production brokers. Sharing one broker across environments causes confusing cross-environment task execution.",[66,6158,6159],{},"DNS issues or clock drift can affect broker reachability and periodic scheduling.",[66,6161,6162],{},"If you use Docker, verify the container command was not overridden by a shell script that exits early.",[66,6164,6165],{},"For periodic tasks, avoid running multiple Beat instances unless your scheduler backend is designed for it.",[66,6167,6168,6169,6172],{},"If ",[20,6170,6171],{},"inspect"," commands do not return results, do not assume the worker is down until you confirm remote control behavior and the worker environment.",[11,6174,1386],{"id":1385},[16,6176,6177,6178,211],{},"For background on architecture, see ",[1395,6179,6181],{"href":6180},"\u002Fdeploy\u002Fconfigure-redis-celery-django-production","how Celery works in Django production",[16,6183,6184,6185,3146,6188,211],{},"If you need a clean production setup, follow ",[1395,6186,6187],{"href":6180},"deploy Django with Celery and Redis",[1395,6189,6191],{"href":6190},"\u002Fdeploy\u002Frun-celery-with-systemd-django","run Celery with systemd for Django production",[16,6193,6194,6195,211],{},"For operational recovery, use ",[1395,6196,6197],{"href":6190},"how to restart Django background workers safely in production",[16,6199,6200],{},"You may also want these related troubleshooting guides:",[63,6202,6203,6207,6211,6216],{},[66,6204,6205],{},[1395,6206,4456],{"href":4455},[66,6208,6209],{},[1395,6210,4446],{"href":4445},[66,6212,6213],{},[1395,6214,6215],{"href":4551},"Django Static Files Not Loading in Production: Fix Guide",[66,6217,6218],{},[1395,6219,3000],{"href":2999},[11,6221,1420],{"id":1419},[52,6223,6225],{"id":6224},"why-is-celery-not-running-in-django-production-even-though-the-web-app-works","Why is Celery not running in Django production even though the web app works?",[16,6227,6228],{},"Because the web process and worker process are separate. Django can serve requests normally while the Celery worker is stopped, misconfigured, or unable to connect to the broker.",[52,6230,6232],{"id":6231},"how-do-i-tell-whether-the-problem-is-celery-worker-beat-or-redisrabbitmq","How do I tell whether the problem is Celery worker, Beat, or Redis\u002FRabbitMQ?",[16,6234,6235],{},"Check whether tasks are being queued. If periodic tasks are never queued, Beat is likely the issue. If tasks queue but never run, inspect the worker. If the worker logs show connection failures, inspect the broker.",[52,6237,6239],{"id":6238},"why-are-celery-tasks-queued-but-never-executed-in-production","Why are Celery tasks queued but never executed in production?",[16,6241,6242],{},"Usually the worker is not running, is listening to the wrong queue, cannot deserialize the task, or does not have the task registered. Queue routing mismatches are especially common.",[52,6244,6246],{"id":6245},"should-celery-beat-run-in-the-same-process-as-the-worker-in-production","Should Celery Beat run in the same process as the worker in production?",[16,6248,6249],{},"Usually yes: run Beat as a separate service in production. That makes restarts, monitoring, and failure isolation much clearer.",[52,6251,6253],{"id":6252},"what-should-i-restart-after-deploying-django-code-that-changes-celery-tasks","What should I restart after deploying Django code that changes Celery tasks?",[16,6255,6256],{},"Restart the Celery worker, and restart Beat if periodic task definitions or scheduling code changed. Otherwise, old worker processes may keep running stale code.",[1485,6258,6259],{},"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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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":111,"searchDepth":149,"depth":149,"links":6261},[6262,6263,6264,6265,6268,6277,6283,6287,6292,6297,6301,6304,6307,6313,6317,6318,6319,6320],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":4622,"depth":136,"text":4623,"children":6266},[6267],{"id":4677,"depth":149,"text":4678},{"id":4684,"depth":136,"text":4685,"children":6269},[6270,6271,6272,6273,6274,6275,6276],{"id":1277,"depth":149,"text":1277},{"id":4738,"depth":149,"text":4739},{"id":4776,"depth":149,"text":4669},{"id":4819,"depth":149,"text":4820},{"id":4847,"depth":149,"text":4848},{"id":4949,"depth":149,"text":4678},{"id":4955,"depth":149,"text":4956},{"id":5010,"depth":136,"text":5011,"children":6278},[6279,6280,6281,6282],{"id":5017,"depth":149,"text":5018},{"id":5060,"depth":149,"text":5061},{"id":5092,"depth":149,"text":5093},{"id":5177,"depth":149,"text":4678},{"id":5183,"depth":136,"text":5184,"children":6284},[6285,6286],{"id":5220,"depth":149,"text":5221},{"id":5290,"depth":149,"text":4678},{"id":5334,"depth":136,"text":5335,"children":6288},[6289,6290,6291],{"id":5341,"depth":149,"text":5342},{"id":5417,"depth":149,"text":5418},{"id":5464,"depth":149,"text":4678},{"id":5470,"depth":136,"text":5471,"children":6293},[6294,6295,6296],{"id":5480,"depth":149,"text":5481},{"id":5562,"depth":149,"text":5563},{"id":5581,"depth":149,"text":4678},{"id":5590,"depth":136,"text":5591,"children":6298},[6299,6300],{"id":5597,"depth":149,"text":5598},{"id":5750,"depth":149,"text":4678},{"id":5773,"depth":136,"text":5774,"children":6302},[6303],{"id":5809,"depth":149,"text":4678},{"id":5826,"depth":136,"text":5827,"children":6305},[6306],{"id":5876,"depth":149,"text":4678},{"id":5882,"depth":136,"text":5883,"children":6308},[6309,6310,6311,6312],{"id":5886,"depth":149,"text":1277},{"id":5917,"depth":149,"text":4669},{"id":6029,"depth":149,"text":6030},{"id":6042,"depth":149,"text":4678},{"id":6048,"depth":136,"text":6049,"children":6314},[6315,6316],{"id":6055,"depth":149,"text":6056},{"id":6114,"depth":149,"text":6115},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":6321},[6322,6323,6324,6325,6326],{"id":6224,"depth":149,"text":6225},{"id":6231,"depth":149,"text":6232},{"id":6238,"depth":149,"text":6239},{"id":6245,"depth":149,"text":6246},{"id":6252,"depth":149,"text":6253},"A common production failure is that the Django web app is healthy, but Celery tasks stop running.",{},"\u002Fdebug-celery-not-running-django-production","8",[4455,6332,6333],"\u002Ffix-issues\u002Ffix-gunicorn-worker-timeout-django","\u002Ffix-issues\u002Ffix-django-database-connection-errors-production",{"title":4562,"description":6327},[1557,5319,6336],"redis","debug-celery-not-running-django-production",[1557,5319,6336],"RxKc8rR1PpsdB1nHFbMOnV17M-CF1yyJQXaa-ZCWFtE",{"id":6341,"title":6342,"body":6343,"category":3088,"description":8277,"difficulty":3090,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":8278,"navigation":309,"path":8279,"priority":8280,"related":8281,"role":1553,"section":3098,"seo":8282,"stack":8283,"stem":8284,"tags":8285,"type":1561,"__hash__":8286},"articles\u002Fconfigure-https-lets-encrypt-django.md","Configure HTTPS for Django with Let's Encrypt",{"type":8,"value":6344,"toc":8221},[6345,6347,6350,6353,6356,6358,6361,6381,6384,6386,6390,6394,6397,6412,6415,6419,6422,6455,6463,6495,6498,6502,6505,6535,6538,6559,6566,6570,6574,6577,6615,6618,6622,6629,6640,6645,6656,6660,6663,6667,6671,6674,6697,6700,6703,6707,6710,6743,6746,6825,6828,6859,6863,6866,6878,6881,6914,6916,6927,6931,6935,6938,7116,7119,7190,7194,7197,7271,7274,7278,7281,7306,7311,7316,7320,7330,7334,7338,7341,7396,7399,7402,7406,7412,7425,7428,7431,7469,7476,7480,7532,7534,7544,7548,7551,7554,7573,7576,7580,7584,7607,7613,7616,7630,7633,7637,7640,7662,7665,7695,7698,7719,7723,7726,7749,7761,7765,7769,7772,7800,7803,7820,7824,7827,7846,7853,7865,7869,7872,7885,7888,7892,7895,7923,7926,7928,7931,7937,7940,7944,7950,7952,8021,8023,8028,8033,8040,8047,8049,8053,8056,8076,8079,8091,8094,8101,8107,8119,8122,8126,8129,8142,8145,8148,8152,8155,8172,8175,8179,8182,8196,8199,8215,8218],[11,6346,14],{"id":13},[16,6348,6349],{},"A Django app running over plain HTTP is not safe for production. Login sessions, admin access, CSRF tokens, API traffic, and password resets can all be exposed in transit if TLS is missing or misconfigured.",[16,6351,6352],{},"In most real deployments, Django is not the public TLS endpoint. A reverse proxy such as Nginx or Caddy terminates HTTPS and forwards requests to Gunicorn or Uvicorn. The deployment problem is enabling HTTPS for Django with Let’s Encrypt correctly without causing redirect loops, broken admin sessions, failed certificate renewals, or mixed-content errors.",[16,6354,6355],{},"This guide shows a practical production path for Let’s Encrypt with Nginx, plus a short note for Caddy-based deployments.",[11,6357,30],{"id":29},[16,6359,6360],{},"The recommended production path is:",[1173,6362,6363,6366,6369,6372,6375,6378],{},[66,6364,6365],{},"terminate TLS at the reverse proxy",[66,6367,6368],{},"issue a Let’s Encrypt certificate with Certbot, or use Caddy’s built-in automatic TLS",[66,6370,6371],{},"redirect HTTP to HTTPS at the proxy",[66,6373,6374],{},"configure Django to trust the proxy’s HTTPS header",[66,6376,6377],{},"enable secure cookies and browser HTTPS protections carefully",[66,6379,6380],{},"verify certificate renewal and keep a rollback path",[16,6382,6383],{},"If you are using Nginx in front of Gunicorn or Uvicorn, Certbot plus Nginx is the standard setup.",[11,6385,43],{"id":42},[11,6387,6389],{"id":6388},"_1-confirm-your-production-architecture-before-changing-https","1. Confirm your production architecture before changing HTTPS",[52,6391,6393],{"id":6392},"identify-where-tls-should-terminate","Identify where TLS should terminate",[16,6395,6396],{},"For most Django production setups:",[63,6398,6399,6402,6405],{},[66,6400,6401],{},"public internet -> Nginx or Caddy",[66,6403,6404],{},"reverse proxy -> Gunicorn or Uvicorn",[66,6406,6407,6408,6411],{},"Django app is not directly exposed on ",[20,6409,6410],{},":8000"," or a Unix socket",[16,6413,6414],{},"Do not try to make Django itself handle public TLS unless you have a specific reason. Keep TLS at the reverse proxy.",[52,6416,6418],{"id":6417},"verify-dns-ports-and-firewall-rules","Verify DNS, ports, and firewall rules",[16,6420,6421],{},"Check that your domain points to the correct server:",[106,6423,6425],{"className":108,"code":6424,"language":110,"meta":111,"style":111},"dig example.com +short\ndig www.example.com +short\nnslookup example.com\n",[20,6426,6427,6438,6447],{"__ignoreMap":111},[115,6428,6429,6432,6435],{"class":117,"line":118},[115,6430,6431],{"class":262},"dig",[115,6433,6434],{"class":132}," example.com",[115,6436,6437],{"class":132}," +short\n",[115,6439,6440,6442,6445],{"class":117,"line":136},[115,6441,6431],{"class":262},[115,6443,6444],{"class":132}," www.example.com",[115,6446,6437],{"class":132},[115,6448,6449,6452],{"class":117,"line":149},[115,6450,6451],{"class":262},"nslookup",[115,6453,6454],{"class":132}," example.com\n",[16,6456,6457,6458,3146,6460,6462],{},"Confirm ports ",[20,6459,3808],{},[20,6461,2174],{}," are reachable and that your app is currently working:",[106,6464,6466],{"className":108,"code":6465,"language":110,"meta":111,"style":111},"ss -tulpn | grep -E ':80|:443|:8000'\ncurl -I http:\u002F\u002Fexample.com\n",[20,6467,6468,6486],{"__ignoreMap":111},[115,6469,6470,6473,6476,6478,6480,6483],{"class":117,"line":118},[115,6471,6472],{"class":262},"ss",[115,6474,6475],{"class":202}," -tulpn",[115,6477,579],{"class":121},[115,6479,4838],{"class":262},[115,6481,6482],{"class":202}," -E",[115,6484,6485],{"class":132}," ':80|:443|:8000'\n",[115,6487,6488,6490,6492],{"class":117,"line":136},[115,6489,2764],{"class":262},[115,6491,2767],{"class":202},[115,6493,6494],{"class":132}," http:\u002F\u002Fexample.com\n",[16,6496,6497],{},"Let’s Encrypt HTTP validation normally requires port 80 unless you use a DNS challenge flow.",[52,6499,6501],{"id":6500},"snapshot-current-working-config-for-rollback","Snapshot current working config for rollback",[16,6503,6504],{},"Before any changes, back up the current Nginx config:",[106,6506,6508],{"className":108,"code":6507,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.com \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.com.bak\nsudo cp \u002Fetc\u002Fnginx\u002Fnginx.conf \u002Fetc\u002Fnginx\u002Fnginx.conf.bak\n",[20,6509,6510,6523],{"__ignoreMap":111},[115,6511,6512,6514,6517,6520],{"class":117,"line":118},[115,6513,2001],{"class":262},[115,6515,6516],{"class":132}," cp",[115,6518,6519],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.com",[115,6521,6522],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.com.bak\n",[115,6524,6525,6527,6529,6532],{"class":117,"line":136},[115,6526,2001],{"class":262},[115,6528,6516],{"class":132},[115,6530,6531],{"class":132}," \u002Fetc\u002Fnginx\u002Fnginx.conf",[115,6533,6534],{"class":132}," \u002Fetc\u002Fnginx\u002Fnginx.conf.bak\n",[16,6536,6537],{},"Also note service status:",[106,6539,6541],{"className":108,"code":6540,"language":110,"meta":111,"style":111},"systemctl status nginx\nsystemctl status gunicorn\n",[20,6542,6543,6551],{"__ignoreMap":111},[115,6544,6545,6547,6549],{"class":117,"line":118},[115,6546,1981],{"class":262},[115,6548,1984],{"class":132},[115,6550,1996],{"class":132},[115,6552,6553,6555,6557],{"class":117,"line":136},[115,6554,1981],{"class":262},[115,6556,1984],{"class":132},[115,6558,1987],{"class":132},[16,6560,6561,6562,6565],{},"Rollback note: if HTTPS changes break production, restore the previous Nginx site file, test the config with ",[20,6563,6564],{},"sudo nginx -t",", reload Nginx, and return Django to its last known-good HTTP behavior before re-enabling redirects.",[11,6567,6569],{"id":6568},"_2-install-lets-encrypt-tooling","2. Install Let’s Encrypt tooling",[52,6571,6573],{"id":6572},"install-certbot-for-nginx-based-deployments","Install Certbot for Nginx-based deployments",[16,6575,6576],{},"On Debian or Ubuntu:",[106,6578,6580],{"className":108,"code":6579,"language":110,"meta":111,"style":111},"sudo apt update\nsudo apt install certbot python3-certbot-nginx\ncertbot --version\n",[20,6581,6582,6592,6607],{"__ignoreMap":111},[115,6583,6584,6586,6589],{"class":117,"line":118},[115,6585,2001],{"class":262},[115,6587,6588],{"class":132}," apt",[115,6590,6591],{"class":132}," update\n",[115,6593,6594,6596,6598,6601,6604],{"class":117,"line":136},[115,6595,2001],{"class":262},[115,6597,6588],{"class":132},[115,6599,6600],{"class":132}," install",[115,6602,6603],{"class":132}," certbot",[115,6605,6606],{"class":132}," python3-certbot-nginx\n",[115,6608,6609,6612],{"class":117,"line":149},[115,6610,6611],{"class":262},"certbot",[115,6613,6614],{"class":202}," --version\n",[16,6616,6617],{},"If your distribution recommends snap instead, use the supported package path for that OS. The important point is using a maintained Certbot install and confirming the binary works.",[52,6619,6621],{"id":6620},"decide-between-webroot-and-nginx-plugin-modes","Decide between webroot and nginx plugin modes",[16,6623,6624,6625,6628],{},"Use the ",[1226,6626,6627],{},"Nginx plugin"," when:",[63,6630,6631,6634,6637],{},[66,6632,6633],{},"your Nginx config is standard",[66,6635,6636],{},"you want Certbot to help edit TLS config",[66,6638,6639],{},"you want the simplest path",[16,6641,6624,6642,6628],{},[1226,6643,6644],{},"webroot method",[63,6646,6647,6650,6653],{},[66,6648,6649],{},"you want full manual control of Nginx",[66,6651,6652],{},"your proxy layout is custom",[66,6654,6655],{},"you want Certbot to issue certs without rewriting site config",[52,6657,6659],{"id":6658},"note-when-caddy-removes-the-need-for-certbot","Note when Caddy removes the need for Certbot",[16,6661,6662],{},"If you use Caddy, it can obtain and renew certificates automatically. You still need Django HTTPS-aware settings such as secure cookies and proxy SSL headers. The Django-side settings in this guide still apply.",[11,6664,6666],{"id":6665},"_3-issue-a-lets-encrypt-certificate-for-your-django-domain","3. Issue a Let’s Encrypt certificate for your Django domain",[52,6668,6670],{"id":6669},"request-a-certificate-with-the-nginx-plugin","Request a certificate with the Nginx plugin",[16,6672,6673],{},"For a standard Nginx deployment:",[106,6675,6677],{"className":108,"code":6676,"language":110,"meta":111,"style":111},"sudo certbot --nginx -d example.com -d www.example.com\n",[20,6678,6679],{"__ignoreMap":111},[115,6680,6681,6683,6685,6688,6690,6692,6694],{"class":117,"line":118},[115,6682,2001],{"class":262},[115,6684,6603],{"class":132},[115,6686,6687],{"class":202}," --nginx",[115,6689,1019],{"class":202},[115,6691,6434],{"class":132},[115,6693,1019],{"class":202},[115,6695,6696],{"class":132}," www.example.com\n",[16,6698,6699],{},"If you only use one hostname, request only that hostname.",[16,6701,6702],{},"You can let Certbot update Nginx automatically, or use it only to obtain certificates and then manage Nginx manually.",[52,6704,6706],{"id":6705},"alternative-request-a-certificate-with-the-webroot-method","Alternative: request a certificate with the webroot method",[16,6708,6709],{},"Create a shared ACME challenge directory:",[106,6711,6713],{"className":108,"code":6712,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fvar\u002Fwww\u002Fcertbot\u002F.well-known\u002Facme-challenge\nsudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fcertbot\n",[20,6714,6715,6727],{"__ignoreMap":111},[115,6716,6717,6719,6722,6724],{"class":117,"line":118},[115,6718,2001],{"class":262},[115,6720,6721],{"class":132}," mkdir",[115,6723,1001],{"class":202},[115,6725,6726],{"class":132}," \u002Fvar\u002Fwww\u002Fcertbot\u002F.well-known\u002Facme-challenge\n",[115,6728,6729,6731,6734,6737,6740],{"class":117,"line":136},[115,6730,2001],{"class":262},[115,6732,6733],{"class":132}," chown",[115,6735,6736],{"class":202}," -R",[115,6738,6739],{"class":132}," www-data:www-data",[115,6741,6742],{"class":132}," \u002Fvar\u002Fwww\u002Fcertbot\n",[16,6744,6745],{},"Nginx location for ACME challenges:",[106,6747,6749],{"className":2154,"code":6748,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    location \u002F.well-known\u002Facme-challenge\u002F {\n        root \u002Fvar\u002Fwww\u002Fcertbot;\n    }\n\n    location \u002F {\n        return 301 https:\u002F\u002F$host$request_uri;\n    }\n}\n",[20,6750,6751,6757,6765,6771,6775,6784,6792,6796,6800,6808,6817,6821],{"__ignoreMap":111},[115,6752,6753,6755],{"class":117,"line":118},[115,6754,2163],{"class":121},[115,6756,2166],{"class":125},[115,6758,6759,6761,6763],{"class":117,"line":136},[115,6760,2171],{"class":121},[115,6762,3808],{"class":202},[115,6764,3811],{"class":125},[115,6766,6767,6769],{"class":117,"line":149},[115,6768,2182],{"class":121},[115,6770,3713],{"class":125},[115,6772,6773],{"class":117,"line":162},[115,6774,310],{"emptyLinePlaceholder":309},[115,6776,6777,6779,6782],{"class":117,"line":175},[115,6778,2214],{"class":121},[115,6780,6781],{"class":262}," \u002F.well-known\u002Facme-challenge\u002F ",[115,6783,2220],{"class":125},[115,6785,6786,6789],{"class":117,"line":350},[115,6787,6788],{"class":121},"        root ",[115,6790,6791],{"class":125},"\u002Fvar\u002Fwww\u002Fcertbot;\n",[115,6793,6794],{"class":117,"line":365},[115,6795,2233],{"class":125},[115,6797,6798],{"class":117,"line":380},[115,6799,310],{"emptyLinePlaceholder":309},[115,6801,6802,6804,6806],{"class":117,"line":487},[115,6803,2214],{"class":121},[115,6805,2268],{"class":262},[115,6807,2220],{"class":125},[115,6809,6810,6813,6815],{"class":117,"line":2095},[115,6811,6812],{"class":121},"        return",[115,6814,3825],{"class":202},[115,6816,3828],{"class":125},[115,6818,6819],{"class":117,"line":2104},[115,6820,2233],{"class":125},[115,6822,6823],{"class":117,"line":2113},[115,6824,2323],{"class":125},[16,6826,6827],{},"Issue the certificate:",[106,6829,6831],{"className":108,"code":6830,"language":110,"meta":111,"style":111},"sudo certbot certonly --webroot -w \u002Fvar\u002Fwww\u002Fcertbot -d example.com -d www.example.com\n",[20,6832,6833],{"__ignoreMap":111},[115,6834,6835,6837,6839,6842,6845,6848,6851,6853,6855,6857],{"class":117,"line":118},[115,6836,2001],{"class":262},[115,6838,6603],{"class":132},[115,6840,6841],{"class":132}," certonly",[115,6843,6844],{"class":202}," --webroot",[115,6846,6847],{"class":202}," -w",[115,6849,6850],{"class":132}," \u002Fvar\u002Fwww\u002Fcertbot",[115,6852,1019],{"class":202},[115,6854,6434],{"class":132},[115,6856,1019],{"class":202},[115,6858,6696],{"class":132},[52,6860,6862],{"id":6861},"verify-certificate-files-and-expiration","Verify certificate files and expiration",[16,6864,6865],{},"Expected live paths:",[63,6867,6868,6873],{},[66,6869,6870],{},[20,6871,6872],{},"\u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem",[66,6874,6875],{},[20,6876,6877],{},"\u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem",[16,6879,6880],{},"Check certificate details:",[106,6882,6884],{"className":108,"code":6883,"language":110,"meta":111,"style":111},"sudo openssl x509 -in \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem -noout -dates -subject -issuer\n",[20,6885,6886],{"__ignoreMap":111},[115,6887,6888,6890,6893,6896,6899,6902,6905,6908,6911],{"class":117,"line":118},[115,6889,2001],{"class":262},[115,6891,6892],{"class":132}," openssl",[115,6894,6895],{"class":132}," x509",[115,6897,6898],{"class":202}," -in",[115,6900,6901],{"class":132}," \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem",[115,6903,6904],{"class":202}," -noout",[115,6906,6907],{"class":202}," -dates",[115,6909,6910],{"class":202}," -subject",[115,6912,6913],{"class":202}," -issuer\n",[16,6915,3515],{},[63,6917,6918,6921,6924],{},[66,6919,6920],{},"correct hostname(s)",[66,6922,6923],{},"expiry date present",[66,6925,6926],{},"no issuance errors from Certbot",[11,6928,6930],{"id":6929},"_4-configure-nginx-to-serve-django-over-https","4. Configure Nginx to serve Django over HTTPS",[52,6932,6934],{"id":6933},"add-the-https-server-block","Add the HTTPS server block",[16,6936,6937],{},"Example Nginx config for Gunicorn on a Unix socket:",[106,6939,6941],{"className":2154,"code":6940,"language":2156,"meta":111,"style":111},"server {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    client_max_body_size 20M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp.sock:;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-Port $server_port;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_redirect off;\n    }\n}\n",[20,6942,6943,6949,6957,6963,6967,6973,6979,6983,6993,6997,7005,7011,7015,7019,7027,7033,7037,7041,7049,7056,7062,7068,7075,7081,7088,7095,7106,7111],{"__ignoreMap":111},[115,6944,6945,6947],{"class":117,"line":118},[115,6946,2163],{"class":121},[115,6948,2166],{"class":125},[115,6950,6951,6953,6955],{"class":117,"line":136},[115,6952,2171],{"class":121},[115,6954,2174],{"class":202},[115,6956,2177],{"class":125},[115,6958,6959,6961],{"class":117,"line":149},[115,6960,2182],{"class":121},[115,6962,3713],{"class":125},[115,6964,6965],{"class":117,"line":162},[115,6966,310],{"emptyLinePlaceholder":309},[115,6968,6969,6971],{"class":117,"line":175},[115,6970,2194],{"class":121},[115,6972,2197],{"class":125},[115,6974,6975,6977],{"class":117,"line":350},[115,6976,2202],{"class":121},[115,6978,2205],{"class":125},[115,6980,6981],{"class":117,"line":365},[115,6982,310],{"emptyLinePlaceholder":309},[115,6984,6985,6988,6991],{"class":117,"line":380},[115,6986,6987],{"class":121},"    client_max_body_size ",[115,6989,6990],{"class":202},"20M",[115,6992,3811],{"class":125},[115,6994,6995],{"class":117,"line":487},[115,6996,310],{"emptyLinePlaceholder":309},[115,6998,6999,7001,7003],{"class":117,"line":2095},[115,7000,2214],{"class":121},[115,7002,2217],{"class":262},[115,7004,2220],{"class":125},[115,7006,7007,7009],{"class":117,"line":2104},[115,7008,2225],{"class":121},[115,7010,2228],{"class":125},[115,7012,7013],{"class":117,"line":2113},[115,7014,2233],{"class":125},[115,7016,7017],{"class":117,"line":2122},[115,7018,310],{"emptyLinePlaceholder":309},[115,7020,7021,7023,7025],{"class":117,"line":2131},[115,7022,2214],{"class":121},[115,7024,2244],{"class":262},[115,7026,2220],{"class":125},[115,7028,7029,7031],{"class":117,"line":2136},[115,7030,2225],{"class":121},[115,7032,2253],{"class":125},[115,7034,7035],{"class":117,"line":2142},[115,7036,2233],{"class":125},[115,7038,7039],{"class":117,"line":2273},[115,7040,310],{"emptyLinePlaceholder":309},[115,7042,7043,7045,7047],{"class":117,"line":2282},[115,7044,2214],{"class":121},[115,7046,2268],{"class":262},[115,7048,2220],{"class":125},[115,7050,7051,7053],{"class":117,"line":2291},[115,7052,2276],{"class":121},[115,7054,7055],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp.sock:;\n",[115,7057,7058,7060],{"class":117,"line":2299},[115,7059,2285],{"class":121},[115,7061,2288],{"class":125},[115,7063,7064,7066],{"class":117,"line":2307},[115,7065,2285],{"class":121},[115,7067,2296],{"class":125},[115,7069,7070,7072],{"class":117,"line":2315},[115,7071,2285],{"class":121},[115,7073,7074],{"class":125},"X-Forwarded-Port $server_port;\n",[115,7076,7077,7079],{"class":117,"line":2320},[115,7078,2285],{"class":121},[115,7080,2304],{"class":125},[115,7082,7084,7086],{"class":117,"line":7083},24,[115,7085,2285],{"class":121},[115,7087,2312],{"class":125},[115,7089,7091,7093],{"class":117,"line":7090},25,[115,7092,2285],{"class":121},[115,7094,3767],{"class":125},[115,7096,7098,7101,7104],{"class":117,"line":7097},26,[115,7099,7100],{"class":121},"        proxy_redirect ",[115,7102,7103],{"class":202},"off",[115,7105,3811],{"class":125},[115,7107,7109],{"class":117,"line":7108},27,[115,7110,2233],{"class":125},[115,7112,7114],{"class":117,"line":7113},28,[115,7115,2323],{"class":125},[16,7117,7118],{},"If your app listens on localhost instead of a socket:",[106,7120,7122],{"className":2154,"code":7121,"language":2156,"meta":111,"style":111},"location \u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-Host $host;\n    proxy_set_header X-Forwarded-Port $server_port;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_redirect off;\n}\n",[20,7123,7124,7133,7140,7147,7153,7159,7165,7171,7177,7186],{"__ignoreMap":111},[115,7125,7126,7129,7131],{"class":117,"line":118},[115,7127,7128],{"class":121},"location",[115,7130,2268],{"class":262},[115,7132,2220],{"class":125},[115,7134,7135,7138],{"class":117,"line":136},[115,7136,7137],{"class":121},"    proxy_pass ",[115,7139,3748],{"class":125},[115,7141,7142,7145],{"class":117,"line":149},[115,7143,7144],{"class":121},"    proxy_set_header ",[115,7146,2288],{"class":125},[115,7148,7149,7151],{"class":117,"line":162},[115,7150,7144],{"class":121},[115,7152,2296],{"class":125},[115,7154,7155,7157],{"class":117,"line":175},[115,7156,7144],{"class":121},[115,7158,7074],{"class":125},[115,7160,7161,7163],{"class":117,"line":350},[115,7162,7144],{"class":121},[115,7164,2304],{"class":125},[115,7166,7167,7169],{"class":117,"line":365},[115,7168,7144],{"class":121},[115,7170,2312],{"class":125},[115,7172,7173,7175],{"class":117,"line":380},[115,7174,7144],{"class":121},[115,7176,3767],{"class":125},[115,7178,7179,7182,7184],{"class":117,"line":487},[115,7180,7181],{"class":121},"    proxy_redirect ",[115,7183,7103],{"class":202},[115,7185,3811],{"class":125},[115,7187,7188],{"class":117,"line":2095},[115,7189,2323],{"class":125},[52,7191,7193],{"id":7192},"redirect-http-to-https","Redirect HTTP to HTTPS",[16,7195,7196],{},"Use a dedicated HTTP server block:",[106,7198,7199],{"className":2154,"code":6748,"language":2156,"meta":111,"style":111},[20,7200,7201,7207,7215,7221,7225,7233,7239,7243,7247,7255,7263,7267],{"__ignoreMap":111},[115,7202,7203,7205],{"class":117,"line":118},[115,7204,2163],{"class":121},[115,7206,2166],{"class":125},[115,7208,7209,7211,7213],{"class":117,"line":136},[115,7210,2171],{"class":121},[115,7212,3808],{"class":202},[115,7214,3811],{"class":125},[115,7216,7217,7219],{"class":117,"line":149},[115,7218,2182],{"class":121},[115,7220,3713],{"class":125},[115,7222,7223],{"class":117,"line":162},[115,7224,310],{"emptyLinePlaceholder":309},[115,7226,7227,7229,7231],{"class":117,"line":175},[115,7228,2214],{"class":121},[115,7230,6781],{"class":262},[115,7232,2220],{"class":125},[115,7234,7235,7237],{"class":117,"line":350},[115,7236,6788],{"class":121},[115,7238,6791],{"class":125},[115,7240,7241],{"class":117,"line":365},[115,7242,2233],{"class":125},[115,7244,7245],{"class":117,"line":380},[115,7246,310],{"emptyLinePlaceholder":309},[115,7248,7249,7251,7253],{"class":117,"line":487},[115,7250,2214],{"class":121},[115,7252,2268],{"class":262},[115,7254,2220],{"class":125},[115,7256,7257,7259,7261],{"class":117,"line":2095},[115,7258,6812],{"class":121},[115,7260,3825],{"class":202},[115,7262,3828],{"class":125},[115,7264,7265],{"class":117,"line":2104},[115,7266,2233],{"class":125},[115,7268,7269],{"class":117,"line":2113},[115,7270,2323],{"class":125},[16,7272,7273],{},"This preserves the original host and request URI.",[52,7275,7277],{"id":7276},"pass-the-correct-proxy-headers-to-django","Pass the correct proxy headers to Django",[16,7279,7280],{},"The key headers are:",[63,7282,7283,7287,7292,7297,7301],{},[66,7284,7285],{},[20,7286,3648],{},[66,7288,7289],{},[20,7290,7291],{},"X-Forwarded-Host",[66,7293,7294],{},[20,7295,7296],{},"X-Forwarded-Port",[66,7298,7299],{},[20,7300,3203],{},[66,7302,7303],{},[20,7304,7305],{},"X-Forwarded-For",[16,7307,7308,7310],{},[20,7309,3203],{}," is what allows Django to understand the original request was secure when behind the proxy.",[16,7312,4038,7313,7315],{},[20,7314,2377],{}," when Django is behind a reverse proxy you control, and ensure the proxy overwrites forwarded headers instead of passing untrusted client-supplied values through.",[52,7317,7319],{"id":7318},"keep-static-and-media-handling-intact-after-enabling-tls","Keep static and media handling intact after enabling TLS",[16,7321,7322,7323,4493,7326,7329],{},"If Nginx serves static or media files today, keep those locations unchanged in the HTTPS block. Do not switch to HTTPS and accidentally remove ",[20,7324,7325],{},"location \u002Fstatic\u002F",[20,7327,7328],{},"location \u002Fmedia\u002F",", or you may break admin CSS, uploads, or app assets.",[11,7331,7333],{"id":7332},"_5-update-django-for-secure-https-operation","5. Update Django for secure HTTPS operation",[52,7335,7337],{"id":7336},"set-secure-proxy-and-cookie-settings","Set secure proxy and cookie settings",[16,7339,7340],{},"In Django settings:",[106,7342,7344],{"className":2369,"code":7343,"language":1114,"meta":111,"style":111},"DEBUG = False\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n",[20,7345,7346,7356,7360,7376,7380,7388],{"__ignoreMap":111},[115,7347,7348,7351,7353],{"class":117,"line":118},[115,7349,7350],{"class":202},"DEBUG",[115,7352,2380],{"class":121},[115,7354,7355],{"class":202}," False\n",[115,7357,7358],{"class":117,"line":136},[115,7359,310],{"emptyLinePlaceholder":309},[115,7361,7362,7364,7366,7368,7370,7372,7374],{"class":117,"line":149},[115,7363,2377],{"class":202},[115,7365,2380],{"class":121},[115,7367,2383],{"class":125},[115,7369,2386],{"class":132},[115,7371,1153],{"class":125},[115,7373,2391],{"class":132},[115,7375,2394],{"class":125},[115,7377,7378],{"class":117,"line":162},[115,7379,310],{"emptyLinePlaceholder":309},[115,7381,7382,7384,7386],{"class":117,"line":175},[115,7383,2417],{"class":202},[115,7385,2380],{"class":121},[115,7387,2412],{"class":202},[115,7389,7390,7392,7394],{"class":117,"line":350},[115,7391,2426],{"class":202},[115,7393,2380],{"class":121},[115,7395,2412],{"class":202},[16,7397,7398],{},"If settings come from environment variables, update your production environment and restart the app service safely.",[16,7400,7401],{},"For example, restart Gunicorn or your ASGI service after changing Django security settings so the new values are loaded.",[52,7403,7405],{"id":7404},"enable-browser-side-https-protections","Enable browser-side HTTPS protections",[16,7407,7408,7409,7411],{},"After verifying the proxy is correctly sending ",[20,7410,3203],{},", enable redirect enforcement:",[106,7413,7415],{"className":2369,"code":7414,"language":1114,"meta":111,"style":111},"SECURE_SSL_REDIRECT = True\n",[20,7416,7417],{"__ignoreMap":111},[115,7418,7419,7421,7423],{"class":117,"line":118},[115,7420,2407],{"class":202},[115,7422,2380],{"class":121},[115,7424,2412],{"class":202},[16,7426,7427],{},"Only do this when application traffic is coming through the reverse proxy. If Django is still reachable directly over plain HTTP for any path, health check, or internal route, fix that first or scope those checks carefully.",[16,7429,7430],{},"Then stage HSTS carefully:",[106,7432,7434],{"className":2369,"code":7433,"language":1114,"meta":111,"style":111},"SECURE_HSTS_SECONDS = 300\n# Later increase after validation:\n# SECURE_HSTS_SECONDS = 31536000\n\nSECURE_HSTS_INCLUDE_SUBDOMAINS = False\n",[20,7435,7436,7446,7451,7456,7460],{"__ignoreMap":111},[115,7437,7438,7441,7443],{"class":117,"line":118},[115,7439,7440],{"class":202},"SECURE_HSTS_SECONDS",[115,7442,2380],{"class":121},[115,7444,7445],{"class":202}," 300\n",[115,7447,7448],{"class":117,"line":136},[115,7449,7450],{"class":3861},"# Later increase after validation:\n",[115,7452,7453],{"class":117,"line":149},[115,7454,7455],{"class":3861},"# SECURE_HSTS_SECONDS = 31536000\n",[115,7457,7458],{"class":117,"line":162},[115,7459,310],{"emptyLinePlaceholder":309},[115,7461,7462,7465,7467],{"class":117,"line":175},[115,7463,7464],{"class":202},"SECURE_HSTS_INCLUDE_SUBDOMAINS",[115,7466,2380],{"class":121},[115,7468,7355],{"class":202},[16,7470,7471,7472,7475],{},"Do ",[1226,7473,7474],{},"not"," enable preload early. Do not include subdomains unless every relevant subdomain is already HTTPS-ready.",[52,7477,7479],{"id":7478},"review-allowed-hosts-and-csrf-trusted-origins","Review allowed hosts and CSRF trusted origins",[106,7481,7483],{"className":2369,"code":7482,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\n\nCSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fexample.com\",\n    \"https:\u002F\u002Fwww.example.com\",\n]\n",[20,7484,7485,7504,7508,7516,7522,7528],{"__ignoreMap":111},[115,7486,7487,7489,7491,7494,7497,7499,7502],{"class":117,"line":118},[115,7488,2719],{"class":202},[115,7490,2380],{"class":121},[115,7492,7493],{"class":125}," [",[115,7495,7496],{"class":132},"\"example.com\"",[115,7498,1153],{"class":125},[115,7500,7501],{"class":132},"\"www.example.com\"",[115,7503,2552],{"class":125},[115,7505,7506],{"class":117,"line":136},[115,7507,310],{"emptyLinePlaceholder":309},[115,7509,7510,7512,7514],{"class":117,"line":149},[115,7511,2725],{"class":202},[115,7513,2380],{"class":121},[115,7515,3540],{"class":125},[115,7517,7518,7520],{"class":117,"line":162},[115,7519,3582],{"class":132},[115,7521,3354],{"class":125},[115,7523,7524,7526],{"class":117,"line":175},[115,7525,3589],{"class":132},[115,7527,3354],{"class":125},[115,7529,7530],{"class":117,"line":350},[115,7531,2552],{"class":125},[16,7533,3515],{},[63,7535,7536,7538,7541],{},[66,7537,1137],{},[66,7539,7540],{},"form submissions succeed",[66,7542,7543],{},"no CSRF errors after switching to HTTPS",[52,7545,7547],{"id":7546},"decide-where-to-send-hsts-headers","Decide where to send HSTS headers",[16,7549,7550],{},"If Nginx fully terminates TLS, many teams prefer to send HSTS at the proxy layer instead of Django. Django HSTS settings are still valid, but choose one place deliberately and avoid conflicting behavior during rollout.",[16,7552,7553],{},"A typical Nginx header looks like this:",[106,7555,7557],{"className":2154,"code":7556,"language":2156,"meta":111,"style":111},"add_header Strict-Transport-Security \"max-age=300\" always;\n",[20,7558,7559],{"__ignoreMap":111},[115,7560,7561,7564,7567,7570],{"class":117,"line":118},[115,7562,7563],{"class":121},"add_header ",[115,7565,7566],{"class":125},"Strict-Transport-Security ",[115,7568,7569],{"class":132},"\"max-age=300\"",[115,7571,7572],{"class":125}," always;\n",[16,7574,7575],{},"If you manage HSTS at Nginx, keep the value staged the same way: start low, validate, then increase.",[11,7577,7579],{"id":7578},"_6-reload-services-safely-and-verify-https-end-to-end","6. Reload services safely and verify HTTPS end to end",[52,7581,7583],{"id":7582},"test-nginx-config-before-reload","Test Nginx config before reload",[106,7585,7587],{"className":108,"code":7586,"language":110,"meta":111,"style":111},"sudo nginx -t\nsudo systemctl reload nginx\n",[20,7588,7589,7597],{"__ignoreMap":111},[115,7590,7591,7593,7595],{"class":117,"line":118},[115,7592,2001],{"class":262},[115,7594,3906],{"class":132},[115,7596,4282],{"class":202},[115,7598,7599,7601,7603,7605],{"class":117,"line":136},[115,7600,2001],{"class":262},[115,7602,3480],{"class":132},[115,7604,3919],{"class":132},[115,7606,1996],{"class":132},[16,7608,6168,7609,7612],{},[20,7610,7611],{},"nginx -t"," fails, stop and fix the config before reload.",[16,7614,7615],{},"If you changed Django settings, restart the app service as well. For example:",[106,7617,7618],{"className":108,"code":3471,"language":110,"meta":111,"style":111},[20,7619,7620],{"__ignoreMap":111},[115,7621,7622,7624,7626,7628],{"class":117,"line":118},[115,7623,2001],{"class":262},[115,7625,3480],{"class":132},[115,7627,3483],{"class":132},[115,7629,1987],{"class":132},[16,7631,7632],{},"Adjust the service name to match your deployment.",[52,7634,7636],{"id":7635},"verify-tls-and-redirects","Verify TLS and redirects",[16,7638,7639],{},"Check redirect behavior:",[106,7641,7643],{"className":108,"code":7642,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\ncurl -Ik https:\u002F\u002Fexample.com\n",[20,7644,7645,7653],{"__ignoreMap":111},[115,7646,7647,7649,7651],{"class":117,"line":118},[115,7648,2764],{"class":262},[115,7650,2767],{"class":202},[115,7652,6494],{"class":132},[115,7654,7655,7657,7660],{"class":117,"line":136},[115,7656,2764],{"class":262},[115,7658,7659],{"class":202}," -Ik",[115,7661,2770],{"class":132},[16,7663,7664],{},"Inspect the live certificate:",[106,7666,7668],{"className":108,"code":7667,"language":110,"meta":111,"style":111},"openssl s_client -connect example.com:443 -servername example.com \u003C\u002Fdev\u002Fnull\n",[20,7669,7670],{"__ignoreMap":111},[115,7671,7672,7675,7678,7681,7684,7687,7689,7692],{"class":117,"line":118},[115,7673,7674],{"class":262},"openssl",[115,7676,7677],{"class":132}," s_client",[115,7679,7680],{"class":202}," -connect",[115,7682,7683],{"class":132}," example.com:443",[115,7685,7686],{"class":202}," -servername",[115,7688,6434],{"class":132},[115,7690,7691],{"class":121}," \u003C",[115,7693,7694],{"class":132},"\u002Fdev\u002Fnull\n",[16,7696,7697],{},"Verification checklist:",[63,7699,7700,7710,7713,7716],{},[66,7701,7702,7703,4493,7706,7709],{},"HTTP returns ",[20,7704,7705],{},"301",[20,7707,7708],{},"308"," to HTTPS",[66,7711,7712],{},"HTTPS presents the intended certificate",[66,7714,7715],{},"hostname matches the certificate SAN",[66,7717,7718],{},"no certificate warnings in browser",[52,7720,7722],{"id":7721},"verify-django-behavior-behind-https","Verify Django behavior behind HTTPS",[16,7724,7725],{},"Test in a browser:",[63,7727,7728,7734,7737,7743,7746],{},[66,7729,7730,7731],{},"load the site over ",[20,7732,7733],{},"https:\u002F\u002F",[66,7735,7736],{},"log in to Django admin",[66,7738,7739,7740],{},"inspect cookies for ",[20,7741,7742],{},"Secure",[66,7744,7745],{},"confirm no mixed content warnings in dev tools",[66,7747,7748],{},"confirm no redirect loop between Nginx and Django",[16,7750,7751,7752,7754,7755,7757,7758,7760],{},"If you hit a redirect loop, disable ",[20,7753,2407],{}," temporarily and confirm ",[20,7756,2377],{}," plus Nginx ",[20,7759,3203],{}," are aligned.",[11,7762,7764],{"id":7763},"_7-configure-automatic-certificate-renewal","7. Configure automatic certificate renewal",[52,7766,7768],{"id":7767},"check-the-systemd-timer-or-cron-job-installed-by-certbot","Check the systemd timer or cron job installed by Certbot",[16,7770,7771],{},"Most modern installs use a systemd timer:",[106,7773,7775],{"className":108,"code":7774,"language":110,"meta":111,"style":111},"systemctl list-timers | grep certbot\nsystemctl status certbot.timer\n",[20,7776,7777,7791],{"__ignoreMap":111},[115,7778,7779,7781,7784,7786,7788],{"class":117,"line":118},[115,7780,1981],{"class":262},[115,7782,7783],{"class":132}," list-timers",[115,7785,579],{"class":121},[115,7787,4838],{"class":262},[115,7789,7790],{"class":132}," certbot\n",[115,7792,7793,7795,7797],{"class":117,"line":136},[115,7794,1981],{"class":262},[115,7796,1984],{"class":132},[115,7798,7799],{"class":132}," certbot.timer\n",[16,7801,7802],{},"Test renewal safely:",[106,7804,7806],{"className":108,"code":7805,"language":110,"meta":111,"style":111},"sudo certbot renew --dry-run\n",[20,7807,7808],{"__ignoreMap":111},[115,7809,7810,7812,7814,7817],{"class":117,"line":118},[115,7811,2001],{"class":262},[115,7813,6603],{"class":132},[115,7815,7816],{"class":132}," renew",[115,7818,7819],{"class":202}," --dry-run\n",[52,7821,7823],{"id":7822},"reload-the-reverse-proxy-after-renewal-if-needed","Reload the reverse proxy after renewal if needed",[16,7825,7826],{},"Nginx usually needs a reload to pick up renewed certificates. A deploy hook is a common approach:",[106,7828,7830],{"className":108,"code":7829,"language":110,"meta":111,"style":111},"sudo certbot renew --deploy-hook \"systemctl reload nginx\"\n",[20,7831,7832],{"__ignoreMap":111},[115,7833,7834,7836,7838,7840,7843],{"class":117,"line":118},[115,7835,2001],{"class":262},[115,7837,6603],{"class":132},[115,7839,7816],{"class":132},[115,7841,7842],{"class":202}," --deploy-hook",[115,7844,7845],{"class":132}," \"systemctl reload nginx\"\n",[16,7847,7848,7849,7852],{},"If you use the packaged ",[20,7850,7851],{},"certbot.timer",", test this hook separately and confirm Nginx reloads cleanly with:",[106,7854,7855],{"className":108,"code":4271,"language":110,"meta":111,"style":111},[20,7856,7857],{"__ignoreMap":111},[115,7858,7859,7861,7863],{"class":117,"line":118},[115,7860,2001],{"class":262},[115,7862,3906],{"class":132},[115,7864,4282],{"class":202},[52,7866,7868],{"id":7867},"monitor-renewal-failures","Monitor renewal failures",[16,7870,7871],{},"At minimum, know where logs are and check them periodically:",[106,7873,7875],{"className":108,"code":7874,"language":110,"meta":111,"style":111},"journalctl -u certbot\n",[20,7876,7877],{"__ignoreMap":111},[115,7878,7879,7881,7883],{"class":117,"line":118},[115,7880,2785],{"class":262},[115,7882,2788],{"class":202},[115,7884,7790],{"class":132},[16,7886,7887],{},"A dry-run success plus confirmed Nginx reload covers the minimum operational check.",[11,7889,7891],{"id":7890},"_8-keep-a-simple-rollback-path","8. Keep a simple rollback path",[16,7893,7894],{},"If certificate issuance, redirects, or Django secure settings break production, use a short rollback sequence:",[1173,7896,7897,7900,7905,7911,7917,7920],{},[66,7898,7899],{},"restore the previous Nginx site file from backup",[66,7901,7902,7903],{},"run ",[20,7904,6564],{},[66,7906,7907,7908],{},"reload Nginx with ",[20,7909,7910],{},"sudo systemctl reload nginx",[66,7912,7913,7914,7916],{},"disable ",[20,7915,2407],{}," if Django is looping",[66,7918,7919],{},"restart Gunicorn or Uvicorn so the previous settings are active",[66,7921,7922],{},"verify the site responds over its last known-good path",[16,7924,7925],{},"This rollback does not fix the HTTPS issue, but it gets production back to a stable state while you correct cert paths, proxy headers, or redirect logic.",[11,7927,1321],{"id":1320},[16,7929,7930],{},"This setup works because the reverse proxy handles all public TLS responsibilities while Django remains focused on application logic. Nginx presents the certificate, decrypts HTTPS traffic, and passes requests to Gunicorn or Uvicorn over a local socket or localhost port.",[16,7932,7933,7934,7936],{},"Django then needs one critical piece of trust configuration: ",[20,7935,2377],{},". Without it, Django may think requests are plain HTTP and can mis-handle redirects, secure cookies, and CSRF behavior. That is a common cause of broken Django HTTPS deployments.",[16,7938,7939],{},"Choose the Nginx plugin when speed and simplicity matter. Choose webroot when you want tighter control over proxy config or already manage Nginx as code. If you use Caddy, certificate management becomes simpler, but Django secure settings still must be configured explicitly.",[52,7941,7943],{"id":7942},"when-manual-https-setup-becomes-repetitive","When manual HTTPS setup becomes repetitive",[16,7945,7946,7947,7949],{},"Once you deploy multiple Django apps, the same tasks repeat: Certbot install, ACME path setup, Nginx site generation, ",[20,7948,7611],{},", reload, and renewal checks. Those steps are good candidates for a small script or reusable deployment template. The first automation to add is usually proxy config generation plus renewal verification.",[11,7951,1337],{"id":1336},[63,7953,7954,7960,7966,7976,7986,7996,8002,8011],{},[66,7955,7956,7959],{},[1226,7957,7958],{},"Do not expose Gunicorn or Uvicorn directly"," to the internet. Bind to localhost or a Unix socket.",[66,7961,7962,7965],{},[1226,7963,7964],{},"Static and media paths"," must still resolve after the HTTPS server block is added.",[66,7967,7968,7971,7972,7975],{},[1226,7969,7970],{},"Mixed-content errors"," often come from hardcoded ",[20,7973,7974],{},"http:\u002F\u002F"," asset URLs, old frontend settings, or an incorrect CDN\u002Fstorage base URL.",[66,7977,7978,7981,7982,7985],{},[1226,7979,7980],{},"HSTS rollout"," should be staged. Start with a low value like ",[20,7983,7984],{},"300",", then increase after successful validation.",[66,7987,7988,7991,7992,7995],{},[1226,7989,7990],{},"Private key permissions"," matter. Do not copy ",[20,7993,7994],{},"privkey.pem"," into app directories or broad-access paths.",[66,7997,7998,8001],{},[1226,7999,8000],{},"Certificate issuance can fail"," if DNS is wrong, port 80 is blocked, or another Nginx server block answers the hostname first.",[66,8003,8004,8007,8008,8010],{},[1226,8005,8006],{},"Temporary fallback:"," if HTTPS is breaking production, restore the previous Nginx config, reload Nginx, and disable ",[20,8009,2407],{}," until headers and cert paths are fixed.",[66,8012,8013,8016,8017,8020],{},[1226,8014,8015],{},"Renewal failures"," are operational issues, not just initial setup issues. Test ",[20,8018,8019],{},"certbot renew --dry-run"," before considering the deployment complete.",[11,8022,1386],{"id":1385},[16,8024,8025,8026,211],{},"For the proxy trust model and production checks, read ",[1395,8027,3000],{"href":2999},[16,8029,8030,8031,211],{},"If you need the full WSGI baseline, see ",[1395,8032,2986],{"href":2985},[16,8034,8035,8036,211],{},"If you are deploying an ASGI app, see ",[1395,8037,8039],{"href":8038},"\u002Fdeploy\u002Fdeploy-django-uvicorn-nginx","Deploy Django ASGI with Uvicorn and Nginx",[16,8041,8042,8043,211],{},"If you want automatic TLS at the proxy instead of Certbot, see ",[1395,8044,8046],{"href":8045},"\u002Fdeploy\u002Fdeploy-django-with-caddy-https","Deploy Django with Caddy and Automatic HTTPS",[11,8048,1420],{"id":1419},[52,8050,8052],{"id":8051},"how-do-i-configure-django-so-it-knows-the-original-request-was-https-behind-nginx","How do I configure Django so it knows the original request was HTTPS behind Nginx?",[16,8054,8055],{},"Set:",[106,8057,8058],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,8059,8060],{"__ignoreMap":111},[115,8061,8062,8064,8066,8068,8070,8072,8074],{"class":117,"line":118},[115,8063,2377],{"class":202},[115,8065,2380],{"class":121},[115,8067,2383],{"class":125},[115,8069,2386],{"class":132},[115,8071,1153],{"class":125},[115,8073,2391],{"class":132},[115,8075,2394],{"class":125},[16,8077,8078],{},"And make sure Nginx sends:",[106,8080,8082],{"className":2154,"code":8081,"language":2156,"meta":111,"style":111},"proxy_set_header X-Forwarded-Proto $scheme;\n",[20,8083,8084],{"__ignoreMap":111},[115,8085,8086,8089],{"class":117,"line":118},[115,8087,8088],{"class":121},"proxy_set_header ",[115,8090,2304],{"class":125},[16,8092,8093],{},"Those two pieces must match in practice for HTTPS requests.",[52,8095,8097,8098,8100],{"id":8096},"should-i-enable-secure_ssl_redirect-before-or-after-the-reverse-proxy-is-configured","Should I enable ",[20,8099,2407],{}," before or after the reverse proxy is configured?",[16,8102,8103,8104,8106],{},"After. First confirm Nginx is terminating TLS correctly and forwarding ",[20,8105,3203],{},". Then enable:",[106,8108,8109],{"className":2369,"code":7414,"language":1114,"meta":111,"style":111},[20,8110,8111],{"__ignoreMap":111},[115,8112,8113,8115,8117],{"class":117,"line":118},[115,8114,2407],{"class":202},[115,8116,2380],{"class":121},[115,8118,2412],{"class":202},[16,8120,8121],{},"If enabled too early, Django may create redirect loops or interfere with direct HTTP health checks that should be handled at the proxy layer.",[52,8123,8125],{"id":8124},"what-is-the-safest-way-to-add-hsts-to-a-django-production-deployment","What is the safest way to add HSTS to a Django production deployment?",[16,8127,8128],{},"Start with a low value:",[106,8130,8132],{"className":2369,"code":8131,"language":1114,"meta":111,"style":111},"SECURE_HSTS_SECONDS = 300\n",[20,8133,8134],{"__ignoreMap":111},[115,8135,8136,8138,8140],{"class":117,"line":118},[115,8137,7440],{"class":202},[115,8139,2380],{"class":121},[115,8141,7445],{"class":202},[16,8143,8144],{},"Validate the site over HTTPS for all important flows, then increase gradually. Do not enable preload until you fully understand the long-lived browser impact.",[16,8146,8147],{},"If TLS is fully terminated at Nginx, you can also send HSTS from the proxy instead of Django.",[52,8149,8151],{"id":8150},"why-does-lets-encrypt-certificate-issuance-fail-even-though-my-app-is-running","Why does Let’s Encrypt certificate issuance fail even though my app is running?",[16,8153,8154],{},"Common causes are:",[63,8156,8157,8160,8163,8166,8169],{},[66,8158,8159],{},"DNS still points to the wrong server",[66,8161,8162],{},"port 80 is blocked",[66,8164,8165],{},"Nginx is serving a different hostname block",[66,8167,8168],{},"ACME challenge files are not reachable",[66,8170,8171],{},"firewall or cloud security group blocks HTTP validation",[16,8173,8174],{},"The app itself being reachable on HTTP does not guarantee the ACME challenge path is correct.",[52,8176,8178],{"id":8177},"how-do-i-renew-lets-encrypt-certificates-automatically-without-downtime","How do I renew Let’s Encrypt certificates automatically without downtime?",[16,8180,8181],{},"Use Certbot’s timer or cron-based renewal and confirm it works with:",[106,8183,8184],{"className":108,"code":7805,"language":110,"meta":111,"style":111},[20,8185,8186],{"__ignoreMap":111},[115,8187,8188,8190,8192,8194],{"class":117,"line":118},[115,8189,2001],{"class":262},[115,8191,6603],{"class":132},[115,8193,7816],{"class":132},[115,8195,7819],{"class":202},[16,8197,8198],{},"If needed, reload Nginx after renewal:",[106,8200,8201],{"className":108,"code":7829,"language":110,"meta":111,"style":111},[20,8202,8203],{"__ignoreMap":111},[115,8204,8205,8207,8209,8211,8213],{"class":117,"line":118},[115,8206,2001],{"class":262},[115,8208,6603],{"class":132},[115,8210,7816],{"class":132},[115,8212,7842],{"class":202},[115,8214,7845],{"class":132},[16,8216,8217],{},"A reload is graceful and does not require taking the Django app offline.",[1485,8219,8220],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":8222},[8223,8224,8225,8226,8231,8236,8241,8247,8253,8258,8263,8264,8267,8268,8269],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":6388,"depth":136,"text":6389,"children":8227},[8228,8229,8230],{"id":6392,"depth":149,"text":6393},{"id":6417,"depth":149,"text":6418},{"id":6500,"depth":149,"text":6501},{"id":6568,"depth":136,"text":6569,"children":8232},[8233,8234,8235],{"id":6572,"depth":149,"text":6573},{"id":6620,"depth":149,"text":6621},{"id":6658,"depth":149,"text":6659},{"id":6665,"depth":136,"text":6666,"children":8237},[8238,8239,8240],{"id":6669,"depth":149,"text":6670},{"id":6705,"depth":149,"text":6706},{"id":6861,"depth":149,"text":6862},{"id":6929,"depth":136,"text":6930,"children":8242},[8243,8244,8245,8246],{"id":6933,"depth":149,"text":6934},{"id":7192,"depth":149,"text":7193},{"id":7276,"depth":149,"text":7277},{"id":7318,"depth":149,"text":7319},{"id":7332,"depth":136,"text":7333,"children":8248},[8249,8250,8251,8252],{"id":7336,"depth":149,"text":7337},{"id":7404,"depth":149,"text":7405},{"id":7478,"depth":149,"text":7479},{"id":7546,"depth":149,"text":7547},{"id":7578,"depth":136,"text":7579,"children":8254},[8255,8256,8257],{"id":7582,"depth":149,"text":7583},{"id":7635,"depth":149,"text":7636},{"id":7721,"depth":149,"text":7722},{"id":7763,"depth":136,"text":7764,"children":8259},[8260,8261,8262],{"id":7767,"depth":149,"text":7768},{"id":7822,"depth":149,"text":7823},{"id":7867,"depth":149,"text":7868},{"id":7890,"depth":136,"text":7891},{"id":1320,"depth":136,"text":1321,"children":8265},[8266],{"id":7942,"depth":149,"text":7943},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":8270},[8271,8272,8274,8275,8276],{"id":8051,"depth":149,"text":8052},{"id":8096,"depth":149,"text":8273},"Should I enable SECURE_SSL_REDIRECT before or after the reverse proxy is configured?",{"id":8124,"depth":149,"text":8125},{"id":8150,"depth":149,"text":8151},{"id":8177,"depth":149,"text":8178},"A Django app running over plain HTTP is not safe for production. Login sessions, admin access, CSRF tokens, API traffic, and password resets can all be exposed in transit if TLS...",{},"\u002Fconfigure-https-lets-encrypt-django","17",[2985,8038,2992],{"title":6342,"description":8277},[1557,2156,6611],"configure-https-lets-encrypt-django",[1557,2156,6611],"CNPwX_tsYJ82VwsaQD7NJf_Xs5o9DDTsPwU6UaMOlyQ",{"id":8288,"title":8289,"body":8290,"category":3088,"description":10238,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":10239,"navigation":309,"path":10240,"priority":10241,"related":10242,"role":1553,"section":3098,"seo":10243,"stack":10244,"stem":10245,"tags":10246,"type":1561,"__hash__":10247},"articles\u002Fconfigure-redis-celery-django-production.md","Configure Redis and Celery for Django Production",{"type":8,"value":8291,"toc":10211},[8292,8294,8297,8300,8320,8323,8325,8328,8351,8354,8374,8376,8380,8383,8396,8399,8402,8406,8409,8436,8442,8506,8509,8541,8544,8570,8573,8590,8593,8599,8602,8606,8609,8626,8632,8707,8713,8747,8753,8832,8838,9033,9036,9056,9058,9077,9080,9084,9087,9107,9110,9147,9150,9153,9157,9162,9184,9189,9304,9310,9407,9410,9447,9450,9477,9480,9514,9517,9568,9572,9575,9759,9765,9792,9795,9805,9808,9812,9815,9822,9826,9829,9935,9938,9940,9943,9946,9949,9951,9954,9971,9974,10051,10054,10072,10078,10081,10085,10092,10096,10146,10148,10151,10171,10173,10177,10180,10184,10187,10191,10194,10198,10201,10205,10208],[11,8293,14],{"id":13},[16,8295,8296],{},"Running background jobs in Django production is not just a matter of installing Celery and pointing it at Redis. Your web app, worker processes, broker, and scheduler all have different failure modes and restart behavior.",[16,8298,8299],{},"A naive setup often breaks in predictable ways:",[63,8301,8302,8305,8308,8311,8314,8317],{},[66,8303,8304],{},"Redis is exposed publicly or left unauthenticated",[66,8306,8307],{},"Celery settings are hardcoded in Django settings",[66,8309,8310],{},"workers run in a shell session instead of a service manager",[66,8312,8313],{},"Celery Beat runs more than once and creates duplicate scheduled jobs",[66,8315,8316],{},"long tasks block short tasks in the same queue",[66,8318,8319],{},"deploys restart workers at the wrong time and leave queued or in-flight tasks in a bad state",[16,8321,8322],{},"If you want Django, Celery, and Redis to work reliably in production, treat them as a separate service stack: web, worker, broker, optional scheduler, and verification.",[11,8324,30],{"id":29},[16,8326,8327],{},"A safe default production architecture is:",[63,8329,8330,8333,8336,8339,8342,8345,8348],{},[66,8331,8332],{},"Django web app running under Gunicorn or Uvicorn",[66,8334,8335],{},"Redis on a private interface or managed private service",[66,8337,8338],{},"one or more Celery worker services",[66,8340,8341],{},"one Celery Beat service only if you use periodic tasks",[66,8343,8344],{},"broker URL and related settings loaded from environment variables",[66,8346,8347],{},"systemd or containers used to keep worker processes running",[66,8349,8350],{},"JSON-only serialization, task time limits, and intentional queue design",[16,8352,8353],{},"Minimum safe setup:",[1173,8355,8356,8359,8362,8365,8368,8371],{},[66,8357,8358],{},"Install and secure Redis",[66,8360,8361],{},"Configure Celery in your Django project",[66,8363,8364],{},"Run workers with systemd or containers",[66,8366,8367],{},"Run Beat once only if needed",[66,8369,8370],{},"Verify Redis connectivity, worker registration, and test task execution",[66,8372,8373],{},"Define a rollback path before changing worker code or schedules",[11,8375,43],{"id":42},[52,8377,8379],{"id":8378},"choose-the-production-architecture","Choose the production architecture",[16,8381,8382],{},"For small apps, a single Linux server can run:",[63,8384,8385,8387,8390,8393],{},[66,8386,1878],{},[66,8388,8389],{},"Redis bound to localhost or a private IP",[66,8391,8392],{},"Celery worker",[66,8394,8395],{},"optional Celery Beat",[16,8397,8398],{},"For larger or more sensitive setups, split Redis onto a private internal host or managed Redis service and keep workers on app hosts.",[16,8400,8401],{},"Docker and non-Docker both work. Use the one you already deploy consistently. If your web app uses systemd and a virtualenv, do not introduce Docker only for Celery unless you want the extra operational overhead.",[52,8403,8405],{"id":8404},"install-and-secure-redis-for-production","Install and secure Redis for production",[16,8407,8408],{},"On Ubuntu:",[106,8410,8412],{"className":108,"code":8411,"language":110,"meta":111,"style":111},"sudo apt update\nsudo apt install -y redis-server\n",[20,8413,8414,8422],{"__ignoreMap":111},[115,8415,8416,8418,8420],{"class":117,"line":118},[115,8417,2001],{"class":262},[115,8419,6588],{"class":132},[115,8421,6591],{"class":132},[115,8423,8424,8426,8428,8430,8433],{"class":117,"line":136},[115,8425,2001],{"class":262},[115,8427,6588],{"class":132},[115,8429,6600],{"class":132},[115,8431,8432],{"class":202}," -y",[115,8434,8435],{"class":132}," redis-server\n",[16,8437,8438,8439,241],{},"Edit ",[20,8440,8441],{},"\u002Fetc\u002Fredis\u002Fredis.conf",[106,8443,8447],{"className":8444,"code":8445,"language":8446,"meta":111,"style":111},"language-conf shiki shiki-themes github-light github-dark","bind 127.0.0.1\nprotected-mode yes\nport 6379\n\nrequirepass change-this-to-a-long-random-secret\n\nappendonly yes\nsave 900 1\nsave 300 10\nsave 60 10000\n\nmaxmemory-policy noeviction\n","conf",[20,8448,8449,8454,8459,8464,8468,8473,8477,8482,8487,8492,8497,8501],{"__ignoreMap":111},[115,8450,8451],{"class":117,"line":118},[115,8452,8453],{},"bind 127.0.0.1\n",[115,8455,8456],{"class":117,"line":136},[115,8457,8458],{},"protected-mode yes\n",[115,8460,8461],{"class":117,"line":149},[115,8462,8463],{},"port 6379\n",[115,8465,8466],{"class":117,"line":162},[115,8467,310],{"emptyLinePlaceholder":309},[115,8469,8470],{"class":117,"line":175},[115,8471,8472],{},"requirepass change-this-to-a-long-random-secret\n",[115,8474,8475],{"class":117,"line":350},[115,8476,310],{"emptyLinePlaceholder":309},[115,8478,8479],{"class":117,"line":365},[115,8480,8481],{},"appendonly yes\n",[115,8483,8484],{"class":117,"line":380},[115,8485,8486],{},"save 900 1\n",[115,8488,8489],{"class":117,"line":487},[115,8490,8491],{},"save 300 10\n",[115,8493,8494],{"class":117,"line":2095},[115,8495,8496],{},"save 60 10000\n",[115,8498,8499],{"class":117,"line":2104},[115,8500,310],{"emptyLinePlaceholder":309},[115,8502,8503],{"class":117,"line":2113},[115,8504,8505],{},"maxmemory-policy noeviction\n",[16,8507,8508],{},"Notes:",[63,8510,8511,8517,8523,8529,8535],{},[66,8512,8513,8516],{},[20,8514,8515],{},"bind 127.0.0.1"," keeps Redis local on a single-server setup.",[66,8518,8519,8520,211],{},"If Redis is on another host, bind it to a private interface only, not ",[20,8521,8522],{},"0.0.0.0",[66,8524,8525,8528],{},[20,8526,8527],{},"requirepass"," is acceptable for straightforward deployments. On newer Redis versions, ACLs are also an option.",[66,8530,8531,8534],{},[20,8532,8533],{},"appendonly yes"," improves recovery after restart, but it does not guarantee zero task loss.",[66,8536,8537,8540],{},[20,8538,8539],{},"noeviction"," is safer than silently evicting broker data under memory pressure.",[16,8542,8543],{},"Restart Redis:",[106,8545,8547],{"className":108,"code":8546,"language":110,"meta":111,"style":111},"sudo systemctl restart redis-server\nsudo systemctl enable redis-server\n",[20,8548,8549,8559],{"__ignoreMap":111},[115,8550,8551,8553,8555,8557],{"class":117,"line":118},[115,8552,2001],{"class":262},[115,8554,3480],{"class":132},[115,8556,3483],{"class":132},[115,8558,8435],{"class":132},[115,8560,8561,8563,8565,8568],{"class":117,"line":136},[115,8562,2001],{"class":262},[115,8564,3480],{"class":132},[115,8566,8567],{"class":132}," enable",[115,8569,8435],{"class":132},[16,8571,8572],{},"Verification:",[106,8574,8576],{"className":108,"code":8575,"language":110,"meta":111,"style":111},"redis-cli -a 'change-this-to-a-long-random-secret' ping\n",[20,8577,8578],{"__ignoreMap":111},[115,8579,8580,8582,8585,8588],{"class":117,"line":118},[115,8581,5375],{"class":262},[115,8583,8584],{"class":202}," -a",[115,8586,8587],{"class":132}," 'change-this-to-a-long-random-secret'",[115,8589,5387],{"class":132},[16,8591,8592],{},"Expected output:",[106,8594,8597],{"className":8595,"code":8596,"language":247,"meta":111},[245],"PONG\n",[20,8598,8596],{"__ignoreMap":111},[16,8600,8601],{},"If Redis is remote, also restrict network access with a firewall or security group so only app and worker hosts can connect. If you use a managed Redis service, prefer TLS if connections leave the local host or private trusted network.",[52,8603,8605],{"id":8604},"configure-django-and-celery","Configure Django and Celery",[16,8607,8608],{},"Install dependencies in your app environment:",[106,8610,8612],{"className":108,"code":8611,"language":110,"meta":111,"style":111},"pip install celery redis\n",[20,8613,8614],{"__ignoreMap":111},[115,8615,8616,8619,8621,8623],{"class":117,"line":118},[115,8617,8618],{"class":262},"pip",[115,8620,6600],{"class":132},[115,8622,5005],{"class":132},[115,8624,8625],{"class":132}," redis\n",[16,8627,8628,8629,241],{},"Create ",[20,8630,8631],{},"proj\u002Fcelery.py",[106,8633,8635],{"className":2369,"code":8634,"language":1114,"meta":111,"style":111},"import os\nfrom celery import Celery\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"proj.settings.production\")\n\napp = Celery(\"proj\")\napp.config_from_object(\"django.conf:settings\", namespace=\"CELERY\")\napp.autodiscover_tasks()\n",[20,8636,8637,8643,8653,8657,8670,8674,8687,8703],{"__ignoreMap":111},[115,8638,8639,8641],{"class":117,"line":118},[115,8640,5613],{"class":121},[115,8642,5616],{"class":125},[115,8644,8645,8647,8649,8651],{"class":117,"line":136},[115,8646,5621],{"class":121},[115,8648,5624],{"class":125},[115,8650,5613],{"class":121},[115,8652,5629],{"class":125},[115,8654,8655],{"class":117,"line":149},[115,8656,310],{"emptyLinePlaceholder":309},[115,8658,8659,8661,8663,8665,8668],{"class":117,"line":162},[115,8660,5638],{"class":125},[115,8662,5641],{"class":132},[115,8664,1153],{"class":125},[115,8666,8667],{"class":132},"\"proj.settings.production\"",[115,8669,2394],{"class":125},[115,8671,8672],{"class":117,"line":175},[115,8673,310],{"emptyLinePlaceholder":309},[115,8675,8676,8678,8680,8682,8685],{"class":117,"line":350},[115,8677,5657],{"class":125},[115,8679,129],{"class":121},[115,8681,5662],{"class":125},[115,8683,8684],{"class":132},"\"proj\"",[115,8686,2394],{"class":125},[115,8688,8689,8691,8693,8695,8697,8699,8701],{"class":117,"line":365},[115,8690,5672],{"class":125},[115,8692,5675],{"class":132},[115,8694,1153],{"class":125},[115,8696,5681],{"class":5680},[115,8698,129],{"class":121},[115,8700,5686],{"class":132},[115,8702,2394],{"class":125},[115,8704,8705],{"class":117,"line":380},[115,8706,5693],{"class":125},[16,8708,8709,8710,241],{},"Update ",[20,8711,8712],{},"proj\u002F__init__.py",[106,8714,8715],{"className":2369,"code":5702,"language":1114,"meta":111,"style":111},[20,8716,8717,8731,8735],{"__ignoreMap":111},[115,8718,8719,8721,8723,8725,8727,8729],{"class":117,"line":118},[115,8720,5621],{"class":121},[115,8722,5711],{"class":125},[115,8724,5613],{"class":121},[115,8726,5716],{"class":125},[115,8728,5719],{"class":121},[115,8730,5722],{"class":125},[115,8732,8733],{"class":117,"line":136},[115,8734,310],{"emptyLinePlaceholder":309},[115,8736,8737,8739,8741,8743,8745],{"class":117,"line":149},[115,8738,5731],{"class":202},[115,8740,2380],{"class":121},[115,8742,2383],{"class":125},[115,8744,5738],{"class":132},[115,8746,5741],{"class":125},[16,8748,8749,8750,241],{},"Example task in ",[20,8751,8752],{},"myapp\u002Ftasks.py",[106,8754,8756],{"className":2369,"code":8755,"language":1114,"meta":111,"style":111},"from celery import shared_task\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n@shared_task\ndef healthcheck_task():\n    logger.info(\"Celery test task executed\")\n    return \"ok\"\n",[20,8757,8758,8769,8776,8780,8795,8799,8804,8815,8825],{"__ignoreMap":111},[115,8759,8760,8762,8764,8766],{"class":117,"line":118},[115,8761,5621],{"class":121},[115,8763,5624],{"class":125},[115,8765,5613],{"class":121},[115,8767,8768],{"class":125}," shared_task\n",[115,8770,8771,8773],{"class":117,"line":136},[115,8772,5613],{"class":121},[115,8774,8775],{"class":125}," logging\n",[115,8777,8778],{"class":117,"line":149},[115,8779,310],{"emptyLinePlaceholder":309},[115,8781,8782,8785,8787,8790,8793],{"class":117,"line":162},[115,8783,8784],{"class":125},"logger ",[115,8786,129],{"class":121},[115,8788,8789],{"class":125}," logging.getLogger(",[115,8791,8792],{"class":202},"__name__",[115,8794,2394],{"class":125},[115,8796,8797],{"class":117,"line":175},[115,8798,310],{"emptyLinePlaceholder":309},[115,8800,8801],{"class":117,"line":350},[115,8802,8803],{"class":262},"@shared_task\n",[115,8805,8806,8809,8812],{"class":117,"line":365},[115,8807,8808],{"class":121},"def",[115,8810,8811],{"class":262}," healthcheck_task",[115,8813,8814],{"class":125},"():\n",[115,8816,8817,8820,8823],{"class":117,"line":380},[115,8818,8819],{"class":125},"    logger.info(",[115,8821,8822],{"class":132},"\"Celery test task executed\"",[115,8824,2394],{"class":125},[115,8826,8827,8829],{"class":117,"line":487},[115,8828,3822],{"class":121},[115,8830,8831],{"class":132}," \"ok\"\n",[16,8833,8834,8835,241],{},"In ",[20,8836,8837],{},"settings\u002Fproduction.py",[106,8839,8841],{"className":2369,"code":8840,"language":1114,"meta":111,"style":111},"import os\n\nCELERY_BROKER_URL = os.environ[\"CELERY_BROKER_URL\"]\n\n# Only set a result backend if you actually need task results.\nCELERY_RESULT_BACKEND = os.environ.get(\"CELERY_RESULT_BACKEND\")\nCELERY_RESULT_EXPIRES = 86400\n\nCELERY_ACCEPT_CONTENT = [\"json\"]\nCELERY_TASK_SERIALIZER = \"json\"\nCELERY_RESULT_SERIALIZER = \"json\"\nCELERY_TIMEZONE = \"UTC\"\nCELERY_ENABLE_UTC = True\n\nCELERY_TASK_ALWAYS_EAGER = False\n\nCELERY_TASK_ACKS_LATE = True  # use only for idempotent tasks; a task may run again after worker failure\nCELERY_WORKER_PREFETCH_MULTIPLIER = 1\n\nCELERY_TASK_TIME_LIMIT = 1800\nCELERY_TASK_SOFT_TIME_LIMIT = 1500\n\nCELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True\n",[20,8842,8843,8849,8853,8867,8871,8876,8890,8900,8904,8918,8928,8937,8947,8956,8960,8969,8973,8986,8996,9000,9010,9020,9024],{"__ignoreMap":111},[115,8844,8845,8847],{"class":117,"line":118},[115,8846,5613],{"class":121},[115,8848,5616],{"class":125},[115,8850,8851],{"class":117,"line":136},[115,8852,310],{"emptyLinePlaceholder":309},[115,8854,8855,8857,8859,8862,8865],{"class":117,"line":149},[115,8856,5201],{"class":202},[115,8858,2380],{"class":121},[115,8860,8861],{"class":125}," os.environ[",[115,8863,8864],{"class":132},"\"CELERY_BROKER_URL\"",[115,8866,2552],{"class":125},[115,8868,8869],{"class":117,"line":162},[115,8870,310],{"emptyLinePlaceholder":309},[115,8872,8873],{"class":117,"line":175},[115,8874,8875],{"class":3861},"# Only set a result backend if you actually need task results.\n",[115,8877,8878,8880,8882,8885,8888],{"class":117,"line":350},[115,8879,5206],{"class":202},[115,8881,2380],{"class":121},[115,8883,8884],{"class":125}," os.environ.get(",[115,8886,8887],{"class":132},"\"CELERY_RESULT_BACKEND\"",[115,8889,2394],{"class":125},[115,8891,8892,8895,8897],{"class":117,"line":365},[115,8893,8894],{"class":202},"CELERY_RESULT_EXPIRES",[115,8896,2380],{"class":121},[115,8898,8899],{"class":202}," 86400\n",[115,8901,8902],{"class":117,"line":380},[115,8903,310],{"emptyLinePlaceholder":309},[115,8905,8906,8909,8911,8913,8916],{"class":117,"line":487},[115,8907,8908],{"class":202},"CELERY_ACCEPT_CONTENT",[115,8910,2380],{"class":121},[115,8912,7493],{"class":125},[115,8914,8915],{"class":132},"\"json\"",[115,8917,2552],{"class":125},[115,8919,8920,8923,8925],{"class":117,"line":2095},[115,8921,8922],{"class":202},"CELERY_TASK_SERIALIZER",[115,8924,2380],{"class":121},[115,8926,8927],{"class":132}," \"json\"\n",[115,8929,8930,8933,8935],{"class":117,"line":2104},[115,8931,8932],{"class":202},"CELERY_RESULT_SERIALIZER",[115,8934,2380],{"class":121},[115,8936,8927],{"class":132},[115,8938,8939,8942,8944],{"class":117,"line":2113},[115,8940,8941],{"class":202},"CELERY_TIMEZONE",[115,8943,2380],{"class":121},[115,8945,8946],{"class":132}," \"UTC\"\n",[115,8948,8949,8952,8954],{"class":117,"line":2122},[115,8950,8951],{"class":202},"CELERY_ENABLE_UTC",[115,8953,2380],{"class":121},[115,8955,2412],{"class":202},[115,8957,8958],{"class":117,"line":2131},[115,8959,310],{"emptyLinePlaceholder":309},[115,8961,8962,8965,8967],{"class":117,"line":2136},[115,8963,8964],{"class":202},"CELERY_TASK_ALWAYS_EAGER",[115,8966,2380],{"class":121},[115,8968,7355],{"class":202},[115,8970,8971],{"class":117,"line":2142},[115,8972,310],{"emptyLinePlaceholder":309},[115,8974,8975,8978,8980,8983],{"class":117,"line":2273},[115,8976,8977],{"class":202},"CELERY_TASK_ACKS_LATE",[115,8979,2380],{"class":121},[115,8981,8982],{"class":202}," True",[115,8984,8985],{"class":3861},"  # use only for idempotent tasks; a task may run again after worker failure\n",[115,8987,8988,8991,8993],{"class":117,"line":2282},[115,8989,8990],{"class":202},"CELERY_WORKER_PREFETCH_MULTIPLIER",[115,8992,2380],{"class":121},[115,8994,8995],{"class":202}," 1\n",[115,8997,8998],{"class":117,"line":2291},[115,8999,310],{"emptyLinePlaceholder":309},[115,9001,9002,9005,9007],{"class":117,"line":2299},[115,9003,9004],{"class":202},"CELERY_TASK_TIME_LIMIT",[115,9006,2380],{"class":121},[115,9008,9009],{"class":202}," 1800\n",[115,9011,9012,9015,9017],{"class":117,"line":2307},[115,9013,9014],{"class":202},"CELERY_TASK_SOFT_TIME_LIMIT",[115,9016,2380],{"class":121},[115,9018,9019],{"class":202}," 1500\n",[115,9021,9022],{"class":117,"line":2315},[115,9023,310],{"emptyLinePlaceholder":309},[115,9025,9026,9029,9031],{"class":117,"line":2320},[115,9027,9028],{"class":202},"CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP",[115,9030,2380],{"class":121},[115,9032,2412],{"class":202},[16,9034,9035],{},"Environment file example:",[106,9037,9039],{"className":2329,"code":9038,"language":2331,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=proj.settings.production\nCELERY_BROKER_URL=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F0\nCELERY_RESULT_BACKEND=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F1\n",[20,9040,9041,9046,9051],{"__ignoreMap":111},[115,9042,9043],{"class":117,"line":118},[115,9044,9045],{},"DJANGO_SETTINGS_MODULE=proj.settings.production\n",[115,9047,9048],{"class":117,"line":136},[115,9049,9050],{},"CELERY_BROKER_URL=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F0\n",[115,9052,9053],{"class":117,"line":149},[115,9054,9055],{},"CELERY_RESULT_BACKEND=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F1\n",[16,9057,8572],{},[106,9059,9061],{"className":108,"code":9060,"language":110,"meta":111,"style":111},"python manage.py shell -c \"from myapp.tasks import healthcheck_task; print(healthcheck_task.delay().id)\"\n",[20,9062,9063],{"__ignoreMap":111},[115,9064,9065,9067,9069,9072,9074],{"class":117,"line":118},[115,9066,1114],{"class":262},[115,9068,1117],{"class":132},[115,9070,9071],{"class":132}," shell",[115,9073,1024],{"class":202},[115,9075,9076],{"class":132}," \"from myapp.tasks import healthcheck_task; print(healthcheck_task.delay().id)\"\n",[16,9078,9079],{},"If this errors before workers are started, it still confirms Django can publish to Redis.",[52,9081,9083],{"id":9082},"configure-production-celery-settings","Configure production Celery settings",[16,9085,9086],{},"For safer defaults in production:",[63,9088,9089,9095,9101,9104],{},[66,9090,9091,9094],{},[20,9092,9093],{},"CELERY_TASK_ACKS_LATE = True"," helps avoid losing work if a worker dies mid-task, but only use it for idempotent tasks",[66,9096,9097,9100],{},[20,9098,9099],{},"CELERY_WORKER_PREFETCH_MULTIPLIER = 1"," reduces one worker reserving too many tasks",[66,9102,9103],{},"set soft and hard time limits for untrusted or long-running jobs",[66,9105,9106],{},"use separate queues for long-running tasks",[16,9108,9109],{},"Example queue routing:",[106,9111,9113],{"className":2369,"code":9112,"language":1114,"meta":111,"style":111},"CELERY_TASK_ROUTES = {\n    \"myapp.tasks.generate_report\": {\"queue\": \"long\"},\n}\n",[20,9114,9115,9124,9143],{"__ignoreMap":111},[115,9116,9117,9120,9122],{"class":117,"line":118},[115,9118,9119],{"class":202},"CELERY_TASK_ROUTES",[115,9121,2380],{"class":121},[115,9123,2166],{"class":125},[115,9125,9126,9129,9132,9135,9137,9140],{"class":117,"line":136},[115,9127,9128],{"class":132},"    \"myapp.tasks.generate_report\"",[115,9130,9131],{"class":125},": {",[115,9133,9134],{"class":132},"\"queue\"",[115,9136,2513],{"class":125},[115,9138,9139],{"class":132},"\"long\"",[115,9141,9142],{"class":125},"},\n",[115,9144,9145],{"class":117,"line":149},[115,9146,2323],{"class":125},[16,9148,9149],{},"Then run a separate worker for the long queue if needed.",[16,9151,9152],{},"Do not enable a result backend just by habit. If your app only fires jobs and logs side effects, skipping results reduces Redis load. If you do use Redis as a result backend, set an expiry so old task results do not accumulate indefinitely.",[52,9154,9156],{"id":9155},"run-celery-workers-in-production","Run Celery workers in production",[16,9158,8628,9159,241],{},[20,9160,9161],{},"\u002Fetc\u002Fdefault\u002Fcelery-proj",[106,9163,9165],{"className":2329,"code":9164,"language":2331,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=proj.settings.production\nCELERY_BROKER_URL=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F0\nCELERY_RESULT_BACKEND=redis:\u002F\u002F:change-this-to-a-long-random-secret@127.0.0.1:6379\u002F1\nPATH=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\n",[20,9166,9167,9171,9175,9179],{"__ignoreMap":111},[115,9168,9169],{"class":117,"line":118},[115,9170,9045],{},[115,9172,9173],{"class":117,"line":136},[115,9174,9050],{},[115,9176,9177],{"class":117,"line":149},[115,9178,9055],{},[115,9180,9181],{"class":117,"line":162},[115,9182,9183],{},"PATH=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\n",[16,9185,8628,9186,241],{},[20,9187,9188],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcelery-worker.service",[106,9190,9192],{"className":2026,"code":9191,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Celery Worker for proj\nAfter=network.target redis-server.service\n\n[Service]\nType=simple\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fproj\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fdefault\u002Fcelery-proj\nExecStart=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj worker --loglevel=INFO --concurrency=2\nRestart=always\nRestartSec=5\nTimeoutStopSec=600\n\n[Install]\nWantedBy=multi-user.target\n",[20,9193,9194,9198,9205,9212,9216,9220,9226,9232,9238,9245,9252,9270,9276,9282,9290,9294,9298],{"__ignoreMap":111},[115,9195,9196],{"class":117,"line":118},[115,9197,2035],{"class":262},[115,9199,9200,9202],{"class":117,"line":136},[115,9201,2040],{"class":121},[115,9203,9204],{"class":125},"=Celery Worker for proj\n",[115,9206,9207,9209],{"class":117,"line":149},[115,9208,2048],{"class":121},[115,9210,9211],{"class":125},"=network.target redis-server.service\n",[115,9213,9214],{"class":117,"line":162},[115,9215,310],{"emptyLinePlaceholder":309},[115,9217,9218],{"class":117,"line":175},[115,9219,2060],{"class":262},[115,9221,9222,9224],{"class":117,"line":350},[115,9223,4883],{"class":121},[115,9225,4886],{"class":125},[115,9227,9228,9230],{"class":117,"line":365},[115,9229,2065],{"class":121},[115,9231,2076],{"class":125},[115,9233,9234,9236],{"class":117,"line":380},[115,9235,2073],{"class":121},[115,9237,2076],{"class":125},[115,9239,9240,9242],{"class":117,"line":487},[115,9241,2081],{"class":121},[115,9243,9244],{"class":125},"=\u002Fsrv\u002Fproj\u002Fcurrent\n",[115,9246,9247,9249],{"class":117,"line":2095},[115,9248,2089],{"class":121},[115,9250,9251],{"class":125},"=\u002Fetc\u002Fdefault\u002Fcelery-proj\n",[115,9253,9254,9256,9259,9261,9264,9267],{"class":117,"line":2104},[115,9255,2107],{"class":121},[115,9257,9258],{"class":125},"=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj worker --",[115,9260,4922],{"class":121},[115,9262,9263],{"class":125},"=INFO --",[115,9265,9266],{"class":121},"concurrency",[115,9268,9269],{"class":125},"=2\n",[115,9271,9272,9274],{"class":117,"line":2113},[115,9273,2116],{"class":121},[115,9275,4932],{"class":125},[115,9277,9278,9280],{"class":117,"line":2122},[115,9279,2125],{"class":121},[115,9281,2128],{"class":125},[115,9283,9284,9287],{"class":117,"line":2131},[115,9285,9286],{"class":121},"TimeoutStopSec",[115,9288,9289],{"class":125},"=600\n",[115,9291,9292],{"class":117,"line":2136},[115,9293,310],{"emptyLinePlaceholder":309},[115,9295,9296],{"class":117,"line":2142},[115,9297,2139],{"class":262},[115,9299,9300,9302],{"class":117,"line":2273},[115,9301,2145],{"class":121},[115,9303,2148],{"class":125},[16,9305,9306,9307,241],{},"If you use periodic tasks, create ",[20,9308,9309],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcelery-beat.service",[106,9311,9313],{"className":2026,"code":9312,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Celery Beat for proj\nAfter=network.target redis-server.service\n\n[Service]\nType=simple\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fproj\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fdefault\u002Fcelery-proj\nExecStart=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj beat --loglevel=INFO\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n",[20,9314,9315,9319,9326,9332,9336,9340,9346,9352,9358,9364,9370,9381,9387,9393,9397,9401],{"__ignoreMap":111},[115,9316,9317],{"class":117,"line":118},[115,9318,2035],{"class":262},[115,9320,9321,9323],{"class":117,"line":136},[115,9322,2040],{"class":121},[115,9324,9325],{"class":125},"=Celery Beat for proj\n",[115,9327,9328,9330],{"class":117,"line":149},[115,9329,2048],{"class":121},[115,9331,9211],{"class":125},[115,9333,9334],{"class":117,"line":162},[115,9335,310],{"emptyLinePlaceholder":309},[115,9337,9338],{"class":117,"line":175},[115,9339,2060],{"class":262},[115,9341,9342,9344],{"class":117,"line":350},[115,9343,4883],{"class":121},[115,9345,4886],{"class":125},[115,9347,9348,9350],{"class":117,"line":365},[115,9349,2065],{"class":121},[115,9351,2076],{"class":125},[115,9353,9354,9356],{"class":117,"line":380},[115,9355,2073],{"class":121},[115,9357,2076],{"class":125},[115,9359,9360,9362],{"class":117,"line":487},[115,9361,2081],{"class":121},[115,9363,9244],{"class":125},[115,9365,9366,9368],{"class":117,"line":2095},[115,9367,2089],{"class":121},[115,9369,9251],{"class":125},[115,9371,9372,9374,9377,9379],{"class":117,"line":2104},[115,9373,2107],{"class":121},[115,9375,9376],{"class":125},"=\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj beat --",[115,9378,4922],{"class":121},[115,9380,4925],{"class":125},[115,9382,9383,9385],{"class":117,"line":2113},[115,9384,2116],{"class":121},[115,9386,4932],{"class":125},[115,9388,9389,9391],{"class":117,"line":2122},[115,9390,2125],{"class":121},[115,9392,2128],{"class":125},[115,9394,9395],{"class":117,"line":2131},[115,9396,310],{"emptyLinePlaceholder":309},[115,9398,9399],{"class":117,"line":2136},[115,9400,2139],{"class":262},[115,9402,9403,9405],{"class":117,"line":2142},[115,9404,2145],{"class":121},[115,9406,2148],{"class":125},[16,9408,9409],{},"Load and start:",[106,9411,9413],{"className":108,"code":9412,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable --now celery-worker\nsudo systemctl status celery-worker\n",[20,9414,9415,9423,9437],{"__ignoreMap":111},[115,9416,9417,9419,9421],{"class":117,"line":118},[115,9418,2001],{"class":262},[115,9420,3480],{"class":132},[115,9422,4984],{"class":132},[115,9424,9425,9427,9429,9431,9434],{"class":117,"line":136},[115,9426,2001],{"class":262},[115,9428,3480],{"class":132},[115,9430,8567],{"class":132},[115,9432,9433],{"class":202}," --now",[115,9435,9436],{"class":132}," celery-worker\n",[115,9438,9439,9441,9443,9445],{"class":117,"line":149},[115,9440,2001],{"class":262},[115,9442,3480],{"class":132},[115,9444,1984],{"class":132},[115,9446,9436],{"class":132},[16,9448,9449],{},"If using Beat:",[106,9451,9453],{"className":108,"code":9452,"language":110,"meta":111,"style":111},"sudo systemctl enable --now celery-beat\nsudo systemctl status celery-beat\n",[20,9454,9455,9467],{"__ignoreMap":111},[115,9456,9457,9459,9461,9463,9465],{"class":117,"line":118},[115,9458,2001],{"class":262},[115,9460,3480],{"class":132},[115,9462,8567],{"class":132},[115,9464,9433],{"class":202},[115,9466,4714],{"class":132},[115,9468,9469,9471,9473,9475],{"class":117,"line":136},[115,9470,2001],{"class":262},[115,9472,3480],{"class":132},[115,9474,1984],{"class":132},[115,9476,4714],{"class":132},[16,9478,9479],{},"Log checks:",[106,9481,9483],{"className":108,"code":9482,"language":110,"meta":111,"style":111},"journalctl -u celery-worker -n 100 --no-pager\njournalctl -u celery-beat -n 100 --no-pager\n",[20,9484,9485,9500],{"__ignoreMap":111},[115,9486,9487,9489,9491,9494,9496,9498],{"class":117,"line":118},[115,9488,2785],{"class":262},[115,9490,2788],{"class":202},[115,9492,9493],{"class":132}," celery-worker",[115,9495,2794],{"class":202},[115,9497,2797],{"class":202},[115,9499,2800],{"class":202},[115,9501,9502,9504,9506,9508,9510,9512],{"class":117,"line":136},[115,9503,2785],{"class":262},[115,9505,2788],{"class":202},[115,9507,5051],{"class":132},[115,9509,2794],{"class":202},[115,9511,2797],{"class":202},[115,9513,2800],{"class":202},[16,9515,9516],{},"Environment-safe verification:",[106,9518,9520],{"className":108,"code":9519,"language":110,"meta":111,"style":111},"set -a\n. \u002Fetc\u002Fdefault\u002Fcelery-proj\nset +a\n\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj inspect ping\n\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj inspect active\n",[20,9521,9522,9528,9535,9541,9555],{"__ignoreMap":111},[115,9523,9524,9526],{"class":117,"line":118},[115,9525,203],{"class":202},[115,9527,206],{"class":202},[115,9529,9530,9532],{"class":117,"line":136},[115,9531,211],{"class":202},[115,9533,9534],{"class":132}," \u002Fetc\u002Fdefault\u002Fcelery-proj\n",[115,9536,9537,9539],{"class":117,"line":149},[115,9538,203],{"class":202},[115,9540,221],{"class":132},[115,9542,9543,9546,9548,9551,9553],{"class":117,"line":162},[115,9544,9545],{"class":262},"\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery",[115,9547,5322],{"class":202},[115,9549,9550],{"class":132}," proj",[115,9552,5502],{"class":132},[115,9554,5387],{"class":132},[115,9556,9557,9559,9561,9563,9565],{"class":117,"line":175},[115,9558,9545],{"class":262},[115,9560,5322],{"class":202},[115,9562,9550],{"class":132},[115,9564,5502],{"class":132},[115,9566,9567],{"class":132}," active\n",[52,9569,9571],{"id":9570},"docker-production-variant","Docker production variant",[16,9573,9574],{},"If you deploy with Compose, define separate worker and beat services:",[106,9576,9578],{"className":2485,"code":9577,"language":2487,"meta":111,"style":111},"services:\n  worker:\n    image: your-app-image:latest\n    command: celery -A proj worker --loglevel=INFO --concurrency=2\n    env_file:\n      - .env\n    restart: always\n    depends_on:\n      - redis\n\n  beat:\n    image: your-app-image:latest\n    command: celery -A proj beat --loglevel=INFO\n    env_file:\n      - .env\n    restart: always\n    depends_on:\n      - redis\n\n  redis:\n    image: redis:7\n    restart: always\n    volumes:\n      - .\u002Fredis.conf:\u002Fusr\u002Flocal\u002Fetc\u002Fredis\u002Fredis.conf:ro\n    command: [\"redis-server\", \"\u002Fusr\u002Flocal\u002Fetc\u002Fredis\u002Fredis.conf\"]\n",[20,9579,9580,9586,9592,9601,9610,9616,9622,9630,9637,9644,9648,9654,9662,9671,9677,9683,9691,9697,9703,9707,9713,9721,9729,9736,9743],{"__ignoreMap":111},[115,9581,9582,9584],{"class":117,"line":118},[115,9583,2495],{"class":2494},[115,9585,2498],{"class":125},[115,9587,9588,9590],{"class":117,"line":136},[115,9589,2561],{"class":2494},[115,9591,2498],{"class":125},[115,9593,9594,9596,9598],{"class":117,"line":149},[115,9595,2510],{"class":2494},[115,9597,2513],{"class":125},[115,9599,9600],{"class":132},"your-app-image:latest\n",[115,9602,9603,9605,9607],{"class":117,"line":162},[115,9604,2576],{"class":2494},[115,9606,2513],{"class":125},[115,9608,9609],{"class":132},"celery -A proj worker --loglevel=INFO --concurrency=2\n",[115,9611,9612,9614],{"class":117,"line":175},[115,9613,2521],{"class":2494},[115,9615,2498],{"class":125},[115,9617,9618,9620],{"class":117,"line":350},[115,9619,5976],{"class":125},[115,9621,2526],{"class":132},[115,9623,9624,9626,9628],{"class":117,"line":365},[115,9625,5960],{"class":2494},[115,9627,2513],{"class":125},[115,9629,5965],{"class":132},[115,9631,9632,9635],{"class":117,"line":380},[115,9633,9634],{"class":2494},"    depends_on",[115,9636,2498],{"class":125},[115,9638,9639,9641],{"class":117,"line":487},[115,9640,5976],{"class":125},[115,9642,9643],{"class":132},"redis\n",[115,9645,9646],{"class":117,"line":2095},[115,9647,310],{"emptyLinePlaceholder":309},[115,9649,9650,9652],{"class":117,"line":2104},[115,9651,5987],{"class":2494},[115,9653,2498],{"class":125},[115,9655,9656,9658,9660],{"class":117,"line":2113},[115,9657,2510],{"class":2494},[115,9659,2513],{"class":125},[115,9661,9600],{"class":132},[115,9663,9664,9666,9668],{"class":117,"line":2122},[115,9665,2576],{"class":2494},[115,9667,2513],{"class":125},[115,9669,9670],{"class":132},"celery -A proj beat --loglevel=INFO\n",[115,9672,9673,9675],{"class":117,"line":2131},[115,9674,2521],{"class":2494},[115,9676,2498],{"class":125},[115,9678,9679,9681],{"class":117,"line":2136},[115,9680,5976],{"class":125},[115,9682,2526],{"class":132},[115,9684,9685,9687,9689],{"class":117,"line":2142},[115,9686,5960],{"class":2494},[115,9688,2513],{"class":125},[115,9690,5965],{"class":132},[115,9692,9693,9695],{"class":117,"line":2273},[115,9694,9634],{"class":2494},[115,9696,2498],{"class":125},[115,9698,9699,9701],{"class":117,"line":2282},[115,9700,5976],{"class":125},[115,9702,9643],{"class":132},[115,9704,9705],{"class":117,"line":2291},[115,9706,310],{"emptyLinePlaceholder":309},[115,9708,9709,9711],{"class":117,"line":2299},[115,9710,2598],{"class":2494},[115,9712,2498],{"class":125},[115,9714,9715,9717,9719],{"class":117,"line":2307},[115,9716,2510],{"class":2494},[115,9718,2513],{"class":125},[115,9720,2609],{"class":132},[115,9722,9723,9725,9727],{"class":117,"line":2315},[115,9724,5960],{"class":2494},[115,9726,2513],{"class":125},[115,9728,5965],{"class":132},[115,9730,9731,9734],{"class":117,"line":2320},[115,9732,9733],{"class":2494},"    volumes",[115,9735,2498],{"class":125},[115,9737,9738,9740],{"class":117,"line":7083},[115,9739,5976],{"class":125},[115,9741,9742],{"class":132},".\u002Fredis.conf:\u002Fusr\u002Flocal\u002Fetc\u002Fredis\u002Fredis.conf:ro\n",[115,9744,9745,9747,9749,9752,9754,9757],{"class":117,"line":7090},[115,9746,2576],{"class":2494},[115,9748,2541],{"class":125},[115,9750,9751],{"class":132},"\"redis-server\"",[115,9753,1153],{"class":125},[115,9755,9756],{"class":132},"\"\u002Fusr\u002Flocal\u002Fetc\u002Fredis\u002Fredis.conf\"",[115,9758,2552],{"class":125},[16,9760,9761,9762,241],{},"Example ",[20,9763,9764],{},"redis.conf",[106,9766,9768],{"className":8444,"code":9767,"language":8446,"meta":111,"style":111},"bind 0.0.0.0\nprotected-mode yes\nappendonly yes\nrequirepass your-long-random-password\nmaxmemory-policy noeviction\n",[20,9769,9770,9775,9779,9783,9788],{"__ignoreMap":111},[115,9771,9772],{"class":117,"line":118},[115,9773,9774],{},"bind 0.0.0.0\n",[115,9776,9777],{"class":117,"line":136},[115,9778,8458],{},[115,9780,9781],{"class":117,"line":149},[115,9782,8481],{},[115,9784,9785],{"class":117,"line":162},[115,9786,9787],{},"requirepass your-long-random-password\n",[115,9789,9790],{"class":117,"line":175},[115,9791,8505],{},[16,9793,9794],{},"In container deployments, make sure Redis is reachable only on the internal container network or private network path you intend to use. Do not publish Redis publicly unless you have a specific reason and matching network controls.",[16,9796,9797,9798,9801,9802,211],{},"Important: ",[20,9799,9800],{},"depends_on"," does not guarantee application readiness. Your worker still needs retry behavior on startup, which Celery supports with ",[20,9803,9804],{},"CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True",[16,9806,9807],{},"Run Beat only once in the whole environment.",[52,9809,9811],{"id":9810},"connect-periodic-tasks-safely","Connect periodic tasks safely",[16,9813,9814],{},"Use Celery Beat only if you have scheduled jobs. Never run multiple Beat instances against the same schedule unless you are intentionally using a scheduler with distributed locking.",[16,9816,9817,9818,9821],{},"If schedules are simple and version-controlled, define them in code. If non-developers need to manage schedules, ",[20,9819,9820],{},"django-celery-beat"," can be appropriate, but it adds another database-backed component to operate.",[52,9823,9825],{"id":9824},"verification-checks","Verification checks",[16,9827,9828],{},"After deployment, verify in order:",[1173,9830,9831,9854,9892,9913],{},[66,9832,9833,9834],{},"Redis connectivity:",[106,9835,9837],{"className":108,"code":9836,"language":110,"meta":111,"style":111},"redis-cli -a \"$REDIS_PASSWORD\" ping\n",[20,9838,9839],{"__ignoreMap":111},[115,9840,9841,9843,9845,9847,9850,9852],{"class":117,"line":118},[115,9842,5375],{"class":262},[115,9844,8584],{"class":202},[115,9846,325],{"class":132},[115,9848,9849],{"class":125},"$REDIS_PASSWORD",[115,9851,331],{"class":132},[115,9853,5387],{"class":132},[66,9855,9856,9857],{},"Worker registration:",[106,9858,9860],{"className":108,"code":9859,"language":110,"meta":111,"style":111},"set -a\n. \u002Fetc\u002Fdefault\u002Fcelery-proj\nset +a\ncelery -A proj inspect ping\n",[20,9861,9862,9868,9874,9880],{"__ignoreMap":111},[115,9863,9864,9866],{"class":117,"line":118},[115,9865,203],{"class":202},[115,9867,206],{"class":202},[115,9869,9870,9872],{"class":117,"line":136},[115,9871,211],{"class":202},[115,9873,9534],{"class":132},[115,9875,9876,9878],{"class":117,"line":149},[115,9877,203],{"class":202},[115,9879,221],{"class":132},[115,9881,9882,9884,9886,9888,9890],{"class":117,"line":162},[115,9883,5319],{"class":262},[115,9885,5322],{"class":202},[115,9887,9550],{"class":132},[115,9889,5502],{"class":132},[115,9891,5387],{"class":132},[66,9893,9894,9895],{},"Task execution:",[106,9896,9898],{"className":108,"code":9897,"language":110,"meta":111,"style":111},"python manage.py shell -c \"from myapp.tasks import healthcheck_task; r=healthcheck_task.delay(); print(r.id)\"\n",[20,9899,9900],{"__ignoreMap":111},[115,9901,9902,9904,9906,9908,9910],{"class":117,"line":118},[115,9903,1114],{"class":262},[115,9905,1117],{"class":132},[115,9907,9071],{"class":132},[115,9909,1024],{"class":202},[115,9911,9912],{"class":132}," \"from myapp.tasks import healthcheck_task; r=healthcheck_task.delay(); print(r.id)\"\n",[66,9914,9915,9916],{},"Logs:",[106,9917,9919],{"className":108,"code":9918,"language":110,"meta":111,"style":111},"journalctl -u celery-worker -n 100 --no-pager\n",[20,9920,9921],{"__ignoreMap":111},[115,9922,9923,9925,9927,9929,9931,9933],{"class":117,"line":118},[115,9924,2785],{"class":262},[115,9926,2788],{"class":202},[115,9928,9493],{"class":132},[115,9930,2794],{"class":202},[115,9932,2797],{"class":202},[115,9934,2800],{"class":202},[16,9936,9937],{},"Look for connection failures, permission errors, serializer errors, or import errors.",[11,9939,1321],{"id":1320},[16,9941,9942],{},"Redis is common for Celery because it is simple to operate and fast enough for many Django background job workloads. For small and medium apps, it is usually enough as a broker and sometimes as a result backend.",[16,9944,9945],{},"However, Redis is not the right answer for every queueing problem. If you need more complex broker semantics, stronger delivery guarantees, or very large-scale queueing patterns, another broker may fit better. For many Django teams, though, Redis gives a practical balance between operational complexity and reliability.",[16,9947,9948],{},"Running workers separately from the web process matters because background tasks have different concurrency and timeout behavior. A task worker should be able to restart, scale, and fail independently from Gunicorn. Beat also must be independent because it is a scheduler, not an HTTP process.",[11,9950,6115],{"id":6114},[16,9952,9953],{},"If a worker deploy is bad:",[1173,9955,9956,9959,9962,9965,9968],{},[66,9957,9958],{},"Stop Beat first so it does not enqueue new scheduled jobs.",[66,9960,9961],{},"Check whether workers still have active or reserved tasks before restarting them.",[66,9963,9964],{},"Restart workers back to the previous code or image.",[66,9966,9967],{},"Verify workers can consume the expected queues.",[66,9969,9970],{},"Re-enable Beat only after the queue and logs look healthy.",[16,9972,9973],{},"Systemd example:",[106,9975,9977],{"className":108,"code":9976,"language":110,"meta":111,"style":111},"sudo systemctl stop celery-beat\n\nset -a\n. \u002Fetc\u002Fdefault\u002Fcelery-proj\nset +a\n\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj inspect active\n\u002Fsrv\u002Fproj\u002Fvenv\u002Fbin\u002Fcelery -A proj inspect reserved\n\nsudo systemctl restart celery-worker\n",[20,9978,9979,9990,9994,10000,10006,10012,10024,10037,10041],{"__ignoreMap":111},[115,9980,9981,9983,9985,9988],{"class":117,"line":118},[115,9982,2001],{"class":262},[115,9984,3480],{"class":132},[115,9986,9987],{"class":132}," stop",[115,9989,4714],{"class":132},[115,9991,9992],{"class":117,"line":136},[115,9993,310],{"emptyLinePlaceholder":309},[115,9995,9996,9998],{"class":117,"line":149},[115,9997,203],{"class":202},[115,9999,206],{"class":202},[115,10001,10002,10004],{"class":117,"line":162},[115,10003,211],{"class":202},[115,10005,9534],{"class":132},[115,10007,10008,10010],{"class":117,"line":175},[115,10009,203],{"class":202},[115,10011,221],{"class":132},[115,10013,10014,10016,10018,10020,10022],{"class":117,"line":350},[115,10015,9545],{"class":262},[115,10017,5322],{"class":202},[115,10019,9550],{"class":132},[115,10021,5502],{"class":132},[115,10023,9567],{"class":132},[115,10025,10026,10028,10030,10032,10034],{"class":117,"line":365},[115,10027,9545],{"class":262},[115,10029,5322],{"class":202},[115,10031,9550],{"class":132},[115,10033,5502],{"class":132},[115,10035,10036],{"class":132}," reserved\n",[115,10038,10039],{"class":117,"line":380},[115,10040,310],{"emptyLinePlaceholder":309},[115,10042,10043,10045,10047,10049],{"class":117,"line":487},[115,10044,2001],{"class":262},[115,10046,3480],{"class":132},[115,10048,3483],{"class":132},[115,10050,9436],{"class":132},[16,10052,10053],{},"A few deploy rules matter here:",[63,10055,10056,10059,10062,10069],{},[66,10057,10058],{},"if new code enqueues tasks that old workers do not understand, rollback can still fail",[66,10060,10061],{},"if new workers expect a schema change, apply migrations before they consume migration-dependent tasks",[66,10063,10064,10065,10068],{},"if you use ",[20,10066,10067],{},"acks_late",", interrupted tasks may run again after worker failure or restart",[66,10070,10071],{},"tasks should be idempotent so retries or duplicate execution do not corrupt data",[16,10073,10074,10077],{},[20,10075,10076],{},"TimeoutStopSec=600"," gives workers time to finish work during shutdown, but it is not a substitute for task design. Very long-running tasks may still need separate queues, longer shutdown windows, or a different execution strategy.",[16,10079,10080],{},"If Redis restarts and transient tasks are lost, recovery depends on your persistence mode and whether tasks can be recreated safely. For critical workflows, tasks should be idempotent and externally recoverable where possible.",[52,10082,10084],{"id":10083},"when-to-automate-this-setup","When to automate this setup",[16,10086,10087,10088,10091],{},"Once you repeat this process across environments, convert the Celery files and service definitions into reusable templates. Good candidates are ",[20,10089,10090],{},"celery.py",", hardened production settings, systemd unit files, environment file generation, and post-deploy smoke tests. It is also worth scripting Beat pause and resume around deploys.",[11,10093,10095],{"id":10094},"edge-cases-and-notes","Edge cases and notes",[63,10097,10098,10104,10110,10116,10122,10128,10134,10140],{},[66,10099,10100,10103],{},[1226,10101,10102],{},"Long-running jobs:"," keep them on a separate queue so they do not block short tasks.",[66,10105,10106,10109],{},[1226,10107,10108],{},"CPU-bound tasks:"," Celery concurrency helps less for heavy CPU work; tune worker count carefully based on available cores.",[66,10111,10112,10115],{},[1226,10113,10114],{},"Migrations during deploys:"," if a task depends on a new schema, apply migrations before restarting workers onto code that enqueues or consumes those tasks.",[66,10117,10118,10121],{},[1226,10119,10120],{},"Same server as Gunicorn:"," acceptable for smaller apps, but cap worker concurrency so Celery does not starve the web app of CPU or memory.",[66,10123,10124,10127],{},[1226,10125,10126],{},"Static files:"," Celery does not change static handling directly, but your deploy order still matters: collect static, migrate, restart app, restart workers, then verify.",[66,10129,10130,10133],{},[1226,10131,10132],{},"Multi-instance deployments:"," only one Beat should run, but many worker instances can share the same queues.",[66,10135,10136,10139],{},[1226,10137,10138],{},"Remote Redis:"," private networking is the baseline; add TLS when traffic crosses hosts or leaves a trusted network.",[66,10141,10142,10145],{},[1226,10143,10144],{},"Result backend growth:"," if you keep task results in Redis, use expiry and monitor memory usage.",[11,10147,1386],{"id":1385},[16,10149,10150],{},"To build the full production picture, also read:",[63,10152,10153,10157,10161,10165],{},[66,10154,10155],{},[1395,10156,3000],{"href":2999},[66,10158,10159],{},[1395,10160,2986],{"href":2985},[66,10162,10163],{},[1395,10164,8039],{"href":8038},[66,10166,10167],{},[1395,10168,10170],{"href":10169},"\u002Ffix-issues\u002Fdebug-celery-not-running-django-production","How to debug stuck Celery tasks in production",[11,10172,1420],{"id":1419},[52,10174,10176],{"id":10175},"can-i-run-celery-and-gunicorn-on-the-same-server","Can I run Celery and Gunicorn on the same server?",[16,10178,10179],{},"Yes, for small apps. Keep Redis private, use systemd or containers, and limit Celery concurrency so the worker does not consume all CPU or memory.",[52,10181,10183],{"id":10182},"do-i-need-celery-beat-in-production","Do I need Celery Beat in production?",[16,10185,10186],{},"Only if you run scheduled tasks. If you do, run exactly one Beat instance for that environment.",[52,10188,10190],{"id":10189},"should-redis-be-used-as-both-broker-and-result-backend","Should Redis be used as both broker and result backend?",[16,10192,10193],{},"It can be, but only enable the result backend if your app actually needs task state or returned values. Many production setups use Redis only as a broker.",[52,10195,10197],{"id":10196},"how-many-celery-workers-should-i-run","How many Celery workers should I run?",[16,10199,10200],{},"Start with one worker service and a conservative concurrency value such as 2. Increase based on task type, CPU, memory, and queue backlog. Separate long-running tasks into their own queue before scaling blindly.",[52,10202,10204],{"id":10203},"what-happens-to-queued-tasks-during-a-deploy","What happens to queued tasks during a deploy?",[16,10206,10207],{},"Queued tasks usually remain in Redis if the broker stays up. In-progress tasks may be retried depending on acknowledgment timing and worker shutdown behavior. That is why tasks should be idempotent and deploys should include worker restart and verification steps.",[1485,10209,10210],{},"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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":10212},[10213,10214,10215,10225,10226,10229,10230,10231],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":10216},[10217,10218,10219,10220,10221,10222,10223,10224],{"id":8378,"depth":149,"text":8379},{"id":8404,"depth":149,"text":8405},{"id":8604,"depth":149,"text":8605},{"id":9082,"depth":149,"text":9083},{"id":9155,"depth":149,"text":9156},{"id":9570,"depth":149,"text":9571},{"id":9810,"depth":149,"text":9811},{"id":9824,"depth":149,"text":9825},{"id":1320,"depth":136,"text":1321},{"id":6114,"depth":136,"text":6115,"children":10227},[10228],{"id":10083,"depth":149,"text":10084},{"id":10094,"depth":136,"text":10095},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":10232},[10233,10234,10235,10236,10237],{"id":10175,"depth":149,"text":10176},{"id":10182,"depth":149,"text":10183},{"id":10189,"depth":149,"text":10190},{"id":10196,"depth":149,"text":10197},{"id":10203,"depth":149,"text":10204},"Running background jobs in Django production is not just a matter of installing Celery and pointing it at Redis.",{},"\u002Fconfigure-redis-celery-django-production","22",[6190,2985,8038],{"title":8289,"description":10238},[1557,5319,6336],"configure-redis-celery-django-production",[1557,5319,6336],"E3G5bm0dd53SZRpzM11UL5W0RoofaucBSCuD47aG_Ak",{"id":10249,"title":10250,"body":10251,"category":3088,"description":11631,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":11632,"navigation":309,"path":11633,"priority":11634,"related":11635,"role":1553,"section":3098,"seo":11638,"stack":11639,"stem":11640,"tags":11641,"type":1561,"__hash__":11642},"articles\u002Fconfigure-managed-postgres-for-django.md","Connect Django to Managed PostgreSQL Securely",{"type":8,"value":10252,"toc":11595},[10253,10255,10258,10261,10264,10266,10269,10306,10308,10312,10315,10332,10335,10362,10370,10374,10377,10399,10402,10405,10477,10480,10497,10500,10517,10519,10530,10533,10537,10548,10576,10589,10591,10619,10622,10626,10634,10637,10807,10810,10867,10874,10877,10881,10884,10899,10902,10906,10909,10935,10937,10951,10954,10958,10964,10990,10993,11031,11042,11048,11051,11062,11106,11116,11120,11123,11126,11137,11140,11151,11154,11156,11167,11170,11174,11180,11183,11204,11207,11259,11262,11265,11267,11284,11287,11291,11294,11308,11311,11325,11328,11341,11344,11352,11355,11366,11369,11373,11376,11393,11396,11399,11401,11404,11424,11433,11437,11440,11444,11500,11502,11508,11513,11520,11526,11532,11534,11541,11550,11558,11566,11572,11578,11582,11585,11589,11592],[11,10254,14],{"id":13},[16,10256,10257],{},"Moving a Django app from a local database or self-managed PostgreSQL server to a managed PostgreSQL service sounds simple: update the host, username, and password, then deploy. In production, that is not enough.",[16,10259,10260],{},"The real deployment problem is making the switch without hardcoding credentials, disabling SSL, exposing the database to the internet, or running migrations against the wrong target. A bad cutover can leave the app unable to connect, partially migrated, or pointed at the wrong database with no clean rollback path.",[16,10262,10263],{},"If you want a safe Django-to-managed-PostgreSQL setup, treat the database change as a production deployment task: secrets in environment variables, TLS enforced, network access restricted, connectivity verified before migration, and previous settings preserved for rollback.",[11,10265,30],{"id":29},[16,10267,10268],{},"To connect Django to managed PostgreSQL securely in production:",[1173,10270,10271,10274,10277,10288,10291,10297,10300,10303],{},[66,10272,10273],{},"Store database credentials in environment variables or a secrets manager.",[66,10275,10276],{},"Configure Django with the PostgreSQL backend.",[66,10278,10279,10280,10283,10284,10287],{},"Enforce TLS with ",[20,10281,10282],{},"sslmode=require",", or use ",[20,10285,10286],{},"verify-full"," if your provider supports certificate verification.",[66,10289,10290],{},"Restrict database access to only your app runtime.",[66,10292,10293,10294,211],{},"Test the connection from the application environment before running ",[20,10295,10296],{},"migrate",[66,10298,10299],{},"Take a backup or snapshot before cutover.",[66,10301,10302],{},"Keep the previous database configuration available so you can revert quickly if the new connection fails.",[66,10304,10305],{},"Do not switch app traffic until connectivity checks, secret injection, and your migration plan are confirmed.",[11,10307,43],{"id":42},[11,10309,10311],{"id":10310},"_1-define-what-a-secure-managed-postgresql-connection-looks-like","1) Define what a secure managed PostgreSQL connection looks like",[16,10313,10314],{},"Your production database connection should meet these goals:",[63,10316,10317,10320,10323,10326,10329],{},[66,10318,10319],{},"credentials are not committed to Git",[66,10321,10322],{},"traffic between Django and PostgreSQL is encrypted",[66,10324,10325],{},"only expected application hosts can reach the database",[66,10327,10328],{},"the app can connect before traffic is switched",[66,10330,10331],{},"migrations are run intentionally, with backup and rollback planning",[16,10333,10334],{},"Common mistakes to avoid:",[63,10336,10337,10343,10350,10356,10359],{},[66,10338,10339,10340],{},"hardcoding the password in ",[20,10341,10342],{},"settings.py",[66,10344,10345,10346,10349],{},"allowing ",[20,10347,10348],{},"0.0.0.0\u002F0"," or broad IP ranges unless you have no other option",[66,10351,10352,10353],{},"setting ",[20,10354,10355],{},"sslmode=disable",[66,10357,10358],{},"testing directly against production first",[66,10360,10361],{},"changing the database target without a backup or previous config snapshot",[16,10363,10364,10365,1153,10367,10369],{},"This guide covers the database side only. You still need normal production settings such as correct ",[20,10366,7350],{},[20,10368,2719],{},", HTTPS, and process restarts when secrets change.",[11,10371,10373],{"id":10372},"_2-gather-the-managed-postgresql-connection-details","2) Gather the managed PostgreSQL connection details",[16,10375,10376],{},"From your provider dashboard, collect:",[63,10378,10379,10382,10384,10387,10390,10393,10396],{},[66,10380,10381],{},"host",[66,10383,5446],{},[66,10385,10386],{},"database name",[66,10388,10389],{},"username",[66,10391,10392],{},"password",[66,10394,10395],{},"SSL requirement details",[66,10397,10398],{},"CA certificate path or download, if your provider requires it",[16,10400,10401],{},"Some providers give a full connection string. Others give separate values. Both work.",[16,10403,10404],{},"If you prefer explicit Django settings, use separate environment variables:",[106,10406,10408],{"className":108,"code":10407,"language":110,"meta":111,"style":111},"export DB_NAME=appdb\nexport DB_USER=appuser\nexport DB_PASSWORD='strong-password'\nexport DB_HOST=db.example-provider.com\nexport DB_PORT=5432\nexport DB_SSLMODE=require\n",[20,10409,10410,10421,10432,10443,10454,10465],{"__ignoreMap":111},[115,10411,10412,10414,10416,10418],{"class":117,"line":118},[115,10413,122],{"class":121},[115,10415,126],{"class":125},[115,10417,129],{"class":121},[115,10419,10420],{"class":125},"appdb\n",[115,10422,10423,10425,10427,10429],{"class":117,"line":136},[115,10424,122],{"class":121},[115,10426,141],{"class":125},[115,10428,129],{"class":121},[115,10430,10431],{"class":125},"appuser\n",[115,10433,10434,10436,10438,10440],{"class":117,"line":149},[115,10435,122],{"class":121},[115,10437,154],{"class":125},[115,10439,129],{"class":121},[115,10441,10442],{"class":132},"'strong-password'\n",[115,10444,10445,10447,10449,10451],{"class":117,"line":162},[115,10446,122],{"class":121},[115,10448,167],{"class":125},[115,10450,129],{"class":121},[115,10452,10453],{"class":125},"db.example-provider.com\n",[115,10455,10456,10458,10460,10462],{"class":117,"line":175},[115,10457,122],{"class":121},[115,10459,180],{"class":125},[115,10461,129],{"class":121},[115,10463,10464],{"class":202},"5432\n",[115,10466,10467,10469,10472,10474],{"class":117,"line":350},[115,10468,122],{"class":121},[115,10470,10471],{"class":125}," DB_SSLMODE",[115,10473,129],{"class":121},[115,10475,10476],{"class":125},"require\n",[16,10478,10479],{},"If your provider requires certificate verification, also set:",[106,10481,10483],{"className":108,"code":10482,"language":110,"meta":111,"style":111},"export DB_SSLROOTCERT=\u002Fetc\u002Fssl\u002Fcerts\u002Fprovider-ca.pem\n",[20,10484,10485],{"__ignoreMap":111},[115,10486,10487,10489,10492,10494],{"class":117,"line":118},[115,10488,122],{"class":121},[115,10490,10491],{"class":125}," DB_SSLROOTCERT",[115,10493,129],{"class":121},[115,10495,10496],{"class":125},"\u002Fetc\u002Fssl\u002Fcerts\u002Fprovider-ca.pem\n",[16,10498,10499],{},"If you use a single connection string, keep it in a secret value such as:",[106,10501,10503],{"className":108,"code":10502,"language":110,"meta":111,"style":111},"export DATABASE_URL='postgresql:\u002F\u002Fappuser:password@db.example-provider.com:5432\u002Fappdb?sslmode=require'\n",[20,10504,10505],{"__ignoreMap":111},[115,10506,10507,10509,10512,10514],{"class":117,"line":118},[115,10508,122],{"class":121},[115,10510,10511],{"class":125}," DATABASE_URL",[115,10513,129],{"class":121},[115,10515,10516],{"class":132},"'postgresql:\u002F\u002Fappuser:password@db.example-provider.com:5432\u002Fappdb?sslmode=require'\n",[16,10518,3515],{},[63,10520,10521,10524,10527],{},[66,10522,10523],{},"confirm the hostname, port, and database name match the intended environment",[66,10525,10526],{},"confirm the DNS name resolves from the application runtime",[66,10528,10529],{},"confirm the network path is open from the app host or container, not just from your laptop",[16,10531,10532],{},"Do not point staging at production by accident.",[11,10534,10536],{"id":10535},"_3-install-the-postgresql-driver-django-will-use","3) Install the PostgreSQL driver Django will use",[16,10538,10539,10540,10543,10544,10547],{},"Django needs a PostgreSQL client library. Use either ",[20,10541,10542],{},"psycopg"," (psycopg 3) or ",[20,10545,10546],{},"psycopg2",". Pin the version in your dependency file for reproducible deploys.",[106,10549,10551],{"className":108,"code":10550,"language":110,"meta":111,"style":111},"pip install \"psycopg[binary]\"\n# or, if your project already uses psycopg2 and you build system dependencies:\npip install psycopg2\n",[20,10552,10553,10562,10567],{"__ignoreMap":111},[115,10554,10555,10557,10559],{"class":117,"line":118},[115,10556,8618],{"class":262},[115,10558,6600],{"class":132},[115,10560,10561],{"class":132}," \"psycopg[binary]\"\n",[115,10563,10564],{"class":117,"line":136},[115,10565,10566],{"class":3861},"# or, if your project already uses psycopg2 and you build system dependencies:\n",[115,10568,10569,10571,10573],{"class":117,"line":149},[115,10570,8618],{"class":262},[115,10572,6600],{"class":132},[115,10574,10575],{"class":132}," psycopg2\n",[16,10577,10578,10579,10581,10582,10584,10585,10588],{},"For many production builds, ",[20,10580,10542],{}," is a good default. If your project already uses ",[20,10583,10546],{},", keep it consistent unless you have a reason to switch, but avoid treating ",[20,10586,10587],{},"psycopg2-binary"," as a production default.",[16,10590,3515],{},[106,10592,10594],{"className":108,"code":10593,"language":110,"meta":111,"style":111},"python -c \"import psycopg; print('psycopg ok')\"\n# or\npython -c \"import psycopg2; print('psycopg2 ok')\"\n",[20,10595,10596,10605,10610],{"__ignoreMap":111},[115,10597,10598,10600,10602],{"class":117,"line":118},[115,10599,1114],{"class":262},[115,10601,1024],{"class":202},[115,10603,10604],{"class":132}," \"import psycopg; print('psycopg ok')\"\n",[115,10606,10607],{"class":117,"line":136},[115,10608,10609],{"class":3861},"# or\n",[115,10611,10612,10614,10616],{"class":117,"line":149},[115,10613,1114],{"class":262},[115,10615,1024],{"class":202},[115,10617,10618],{"class":132}," \"import psycopg2; print('psycopg2 ok')\"\n",[16,10620,10621],{},"Rollback note: if the deploy fails because the driver is missing in the runtime image or virtualenv, revert to the previous build artifact or dependency lock before changing database settings.",[11,10623,10625],{"id":10624},"_4-configure-django-database-settings-with-environment-variables","4) Configure Django database settings with environment variables",[52,10627,10629,10630,10633],{"id":10628},"example-using-explicit-databases-settings","Example using explicit ",[20,10631,10632],{},"DATABASES"," settings",[16,10635,10636],{},"This is the most transparent option for a Django PostgreSQL production configuration:",[106,10638,10640],{"className":2369,"code":10639,"language":1114,"meta":111,"style":111},"import os\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": os.environ[\"DB_NAME\"],\n        \"USER\": os.environ[\"DB_USER\"],\n        \"PASSWORD\": os.environ[\"DB_PASSWORD\"],\n        \"HOST\": os.environ[\"DB_HOST\"],\n        \"PORT\": os.environ.get(\"DB_PORT\", \"5432\"),\n        \"CONN_MAX_AGE\": int(os.environ.get(\"DB_CONN_MAX_AGE\", \"60\")),\n        \"OPTIONS\": {\n            \"sslmode\": os.environ.get(\"DB_SSLMODE\", \"require\"),\n        },\n    }\n}\n",[20,10641,10642,10648,10652,10660,10667,10679,10692,10704,10716,10728,10747,10771,10778,10795,10799,10803],{"__ignoreMap":111},[115,10643,10644,10646],{"class":117,"line":118},[115,10645,5613],{"class":121},[115,10647,5616],{"class":125},[115,10649,10650],{"class":117,"line":136},[115,10651,310],{"emptyLinePlaceholder":309},[115,10653,10654,10656,10658],{"class":117,"line":149},[115,10655,10632],{"class":202},[115,10657,2380],{"class":121},[115,10659,2166],{"class":125},[115,10661,10662,10665],{"class":117,"line":162},[115,10663,10664],{"class":132},"    \"default\"",[115,10666,3374],{"class":125},[115,10668,10669,10672,10674,10677],{"class":117,"line":175},[115,10670,10671],{"class":132},"        \"ENGINE\"",[115,10673,2513],{"class":125},[115,10675,10676],{"class":132},"\"django.db.backends.postgresql\"",[115,10678,3354],{"class":125},[115,10680,10681,10684,10687,10690],{"class":117,"line":350},[115,10682,10683],{"class":132},"        \"NAME\"",[115,10685,10686],{"class":125},": os.environ[",[115,10688,10689],{"class":132},"\"DB_NAME\"",[115,10691,3430],{"class":125},[115,10693,10694,10697,10699,10702],{"class":117,"line":365},[115,10695,10696],{"class":132},"        \"USER\"",[115,10698,10686],{"class":125},[115,10700,10701],{"class":132},"\"DB_USER\"",[115,10703,3430],{"class":125},[115,10705,10706,10709,10711,10714],{"class":117,"line":380},[115,10707,10708],{"class":132},"        \"PASSWORD\"",[115,10710,10686],{"class":125},[115,10712,10713],{"class":132},"\"DB_PASSWORD\"",[115,10715,3430],{"class":125},[115,10717,10718,10721,10723,10726],{"class":117,"line":487},[115,10719,10720],{"class":132},"        \"HOST\"",[115,10722,10686],{"class":125},[115,10724,10725],{"class":132},"\"DB_HOST\"",[115,10727,3430],{"class":125},[115,10729,10730,10733,10736,10739,10741,10744],{"class":117,"line":2095},[115,10731,10732],{"class":132},"        \"PORT\"",[115,10734,10735],{"class":125},": os.environ.get(",[115,10737,10738],{"class":132},"\"DB_PORT\"",[115,10740,1153],{"class":125},[115,10742,10743],{"class":132},"\"5432\"",[115,10745,10746],{"class":125},"),\n",[115,10748,10749,10752,10754,10757,10760,10763,10765,10768],{"class":117,"line":2104},[115,10750,10751],{"class":132},"        \"CONN_MAX_AGE\"",[115,10753,2513],{"class":125},[115,10755,10756],{"class":202},"int",[115,10758,10759],{"class":125},"(os.environ.get(",[115,10761,10762],{"class":132},"\"DB_CONN_MAX_AGE\"",[115,10764,1153],{"class":125},[115,10766,10767],{"class":132},"\"60\"",[115,10769,10770],{"class":125},")),\n",[115,10772,10773,10776],{"class":117,"line":2113},[115,10774,10775],{"class":132},"        \"OPTIONS\"",[115,10777,3374],{"class":125},[115,10779,10780,10783,10785,10788,10790,10793],{"class":117,"line":2122},[115,10781,10782],{"class":132},"            \"sslmode\"",[115,10784,10735],{"class":125},[115,10786,10787],{"class":132},"\"DB_SSLMODE\"",[115,10789,1153],{"class":125},[115,10791,10792],{"class":132},"\"require\"",[115,10794,10746],{"class":125},[115,10796,10797],{"class":117,"line":2131},[115,10798,3398],{"class":125},[115,10800,10801],{"class":117,"line":2136},[115,10802,2233],{"class":125},[115,10804,10805],{"class":117,"line":2142},[115,10806,2323],{"class":125},[16,10808,10809],{},"If your provider requires a CA certificate for hostname verification:",[106,10811,10813],{"className":2369,"code":10812,"language":1114,"meta":111,"style":111},"sslrootcert = os.environ.get(\"DB_SSLROOTCERT\")\nif sslrootcert:\n    DATABASES[\"default\"][\"OPTIONS\"][\"sslrootcert\"] = sslrootcert\n",[20,10814,10815,10829,10837],{"__ignoreMap":111},[115,10816,10817,10820,10822,10824,10827],{"class":117,"line":118},[115,10818,10819],{"class":125},"sslrootcert ",[115,10821,129],{"class":121},[115,10823,8884],{"class":125},[115,10825,10826],{"class":132},"\"DB_SSLROOTCERT\"",[115,10828,2394],{"class":125},[115,10830,10831,10834],{"class":117,"line":136},[115,10832,10833],{"class":121},"if",[115,10835,10836],{"class":125}," sslrootcert:\n",[115,10838,10839,10842,10845,10848,10851,10854,10856,10859,10862,10864],{"class":117,"line":149},[115,10840,10841],{"class":202},"    DATABASES",[115,10843,10844],{"class":125},"[",[115,10846,10847],{"class":132},"\"default\"",[115,10849,10850],{"class":125},"][",[115,10852,10853],{"class":132},"\"OPTIONS\"",[115,10855,10850],{"class":125},[115,10857,10858],{"class":132},"\"sslrootcert\"",[115,10860,10861],{"class":125},"] ",[115,10863,129],{"class":121},[115,10865,10866],{"class":125}," sslrootcert\n",[52,10868,10870,10871],{"id":10869},"example-using-database_url","Example using ",[20,10872,10873],{},"DATABASE_URL",[16,10875,10876],{},"If your project already uses URL-based config, that is also fine, but keep SSL options in the URL and keep the whole value in a secret store.",[52,10878,10880],{"id":10879},"keep-secrets-out-of-source-control","Keep secrets out of source control",[16,10882,10883],{},"Do not commit any of these values:",[63,10885,10886,10891,10896],{},[66,10887,10888,10890],{},[20,10889,191],{}," files with production credentials",[66,10892,10893,10894],{},"raw passwords in ",[20,10895,10342],{},[66,10897,10898],{},"copied provider connection strings in docs or examples checked into the repo",[16,10900,10901],{},"Use your platform’s secret injection mechanism, environment variables, or a dedicated secrets manager.",[52,10903,10905],{"id":10904},"safe-defaults-for-production","Safe defaults for production",[16,10907,10908],{},"At minimum:",[63,10910,10911,10920,10929],{},[66,10912,10913,10916,10917],{},[20,10914,10915],{},"ENGINE"," must be ",[20,10918,10919],{},"django.db.backends.postgresql",[66,10921,10922,10925,10926],{},[20,10923,10924],{},"sslmode"," should default to ",[20,10927,10928],{},"require",[66,10930,10931,10934],{},[20,10932,10933],{},"CONN_MAX_AGE"," should be set deliberately, not left as an accident of old settings",[16,10936,3515],{},[106,10938,10939],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,10940,10941],{"__ignoreMap":111},[115,10942,10943,10945,10947,10949],{"class":117,"line":118},[115,10944,1114],{"class":262},[115,10946,1117],{"class":132},[115,10948,1814],{"class":132},[115,10950,1817],{"class":202},[16,10952,10953],{},"This does not validate the database connection itself, but it helps catch nearby production setting issues.",[11,10955,10957],{"id":10956},"_5-enforce-ssltls-for-the-django-to-postgresql-connection","5) Enforce SSL\u002FTLS for the Django-to-PostgreSQL connection",[16,10959,10960,10961,10963],{},"For most managed database providers, ",[20,10962,10282],{}," is the minimum acceptable setting for encryption in transit, but it does not verify the server certificate or hostname.",[106,10965,10967],{"className":2369,"code":10966,"language":1114,"meta":111,"style":111},"\"OPTIONS\": {\n    \"sslmode\": \"require\",\n}\n",[20,10968,10969,10975,10986],{"__ignoreMap":111},[115,10970,10971,10973],{"class":117,"line":118},[115,10972,10853],{"class":132},[115,10974,3374],{"class":125},[115,10976,10977,10980,10982,10984],{"class":117,"line":136},[115,10978,10979],{"class":132},"    \"sslmode\"",[115,10981,2513],{"class":125},[115,10983,10792],{"class":132},[115,10985,3354],{"class":125},[115,10987,10988],{"class":117,"line":149},[115,10989,2323],{"class":125},[16,10991,10992],{},"If your provider supports certificate validation and gives you a CA bundle, use a stricter mode:",[106,10994,10996],{"className":2369,"code":10995,"language":1114,"meta":111,"style":111},"\"OPTIONS\": {\n    \"sslmode\": \"verify-full\",\n    \"sslrootcert\": \"\u002Fetc\u002Fssl\u002Fcerts\u002Fprovider-ca.pem\",\n}\n",[20,10997,10998,11004,11015,11027],{"__ignoreMap":111},[115,10999,11000,11002],{"class":117,"line":118},[115,11001,10853],{"class":132},[115,11003,3374],{"class":125},[115,11005,11006,11008,11010,11013],{"class":117,"line":136},[115,11007,10979],{"class":132},[115,11009,2513],{"class":125},[115,11011,11012],{"class":132},"\"verify-full\"",[115,11014,3354],{"class":125},[115,11016,11017,11020,11022,11025],{"class":117,"line":149},[115,11018,11019],{"class":132},"    \"sslrootcert\"",[115,11021,2513],{"class":125},[115,11023,11024],{"class":132},"\"\u002Fetc\u002Fssl\u002Fcerts\u002Fprovider-ca.pem\"",[115,11026,3354],{"class":125},[115,11028,11029],{"class":117,"line":162},[115,11030,2323],{"class":125},[16,11032,11033,11035,11036,11038,11039,11041],{},[20,11034,10928],{}," encrypts the connection but does not validate server identity. ",[20,11037,10286],{}," encrypts the connection and verifies the server certificate and hostname. Use ",[20,11040,10286],{}," when your provider documentation supports it and you can place the CA certificate on the app host or container image securely.",[16,11043,11044,11045,11047],{},"Provider-specific note: certificate requirements vary. Some providers document ",[20,11046,10928],{}," as the expected setting. Others support or require CA verification. Follow the provider documentation rather than guessing.",[16,11049,11050],{},"Verification check after deploy:",[63,11052,11053,11056,11059],{},[66,11054,11055],{},"confirm the CA file exists if configured",[66,11057,11058],{},"inspect provider dashboards for encrypted sessions if available",[66,11060,11061],{},"verify from PostgreSQL that SSL is active:",[106,11063,11067],{"className":11064,"code":11065,"language":11066,"meta":111,"style":111},"language-sql shiki shiki-themes github-light github-dark","SELECT ssl, version, cipher\nFROM pg_stat_ssl\nWHERE pid = pg_backend_pid();\n","sql",[20,11068,11069,11085,11093],{"__ignoreMap":111},[115,11070,11071,11074,11077,11079,11082],{"class":117,"line":118},[115,11072,11073],{"class":121},"SELECT",[115,11075,11076],{"class":121}," ssl",[115,11078,1153],{"class":125},[115,11080,11081],{"class":121},"version",[115,11083,11084],{"class":125},", cipher\n",[115,11086,11087,11090],{"class":117,"line":136},[115,11088,11089],{"class":121},"FROM",[115,11091,11092],{"class":125}," pg_stat_ssl\n",[115,11094,11095,11098,11101,11103],{"class":117,"line":149},[115,11096,11097],{"class":121},"WHERE",[115,11099,11100],{"class":125}," pid ",[115,11102,129],{"class":121},[115,11104,11105],{"class":125}," pg_backend_pid();\n",[16,11107,11108,11109,11112,11113,11115],{},"You can run that through ",[20,11110,11111],{},"python manage.py dbshell"," only if the PostgreSQL client (",[20,11114,835],{},") is installed in that environment, or execute it through a cursor in Django shell.",[11,11117,11119],{"id":11118},"_6-restrict-database-access-at-the-network-layer","6) Restrict database access at the network layer",[16,11121,11122],{},"Even with SSL enabled, the database should not be broadly reachable.",[16,11124,11125],{},"Prefer:",[63,11127,11128,11131,11134],{},[66,11129,11130],{},"private networking between app and database, if your platform supports it",[66,11132,11133],{},"narrow IP allowlists for app servers or platform egress IPs",[66,11135,11136],{},"separate access rules for staging and production",[16,11138,11139],{},"If your app runs from dynamic IPs, check whether your platform offers:",[63,11141,11142,11145,11148],{},[66,11143,11144],{},"fixed egress IPs",[66,11146,11147],{},"private service networking",[66,11149,11150],{},"a managed connection proxy",[16,11152,11153],{},"Avoid opening the database to all internet sources just to make deployment easier.",[16,11155,3515],{},[63,11157,11158,11161,11164],{},[66,11159,11160],{},"confirm only expected source networks are allowed",[66,11162,11163],{},"test from the application environment, not your laptop only",[66,11165,11166],{},"confirm that rejected sources cannot connect",[16,11168,11169],{},"Rollback note: if the app cannot connect after cutover, check firewall and allowlist rules before changing Django settings again.",[11,11171,11173],{"id":11172},"_7-test-the-connection-before-running-migrations","7) Test the connection before running migrations",[16,11175,11176,11177,11179],{},"Before ",[20,11178,10296],{},", prove that the app can authenticate and execute queries.",[16,11181,11182],{},"Run basic checks:",[106,11184,11186],{"className":108,"code":11185,"language":110,"meta":111,"style":111},"python manage.py showmigrations\npython manage.py shell\n",[20,11187,11188,11196],{"__ignoreMap":111},[115,11189,11190,11192,11194],{"class":117,"line":118},[115,11191,1114],{"class":262},[115,11193,1117],{"class":132},[115,11195,1129],{"class":132},[115,11197,11198,11200,11202],{"class":117,"line":136},[115,11199,1114],{"class":262},[115,11201,1117],{"class":132},[115,11203,6070],{"class":132},[16,11205,11206],{},"In the Django shell:",[106,11208,11210],{"className":2369,"code":11209,"language":1114,"meta":111,"style":111},"from django.db import connection\n\nwith connection.cursor() as cursor:\n    cursor.execute(\"SELECT current_database(), current_user;\")\n    print(cursor.fetchone())\n",[20,11211,11212,11224,11228,11241,11251],{"__ignoreMap":111},[115,11213,11214,11216,11219,11221],{"class":117,"line":118},[115,11215,5621],{"class":121},[115,11217,11218],{"class":125}," django.db ",[115,11220,5613],{"class":121},[115,11222,11223],{"class":125}," connection\n",[115,11225,11226],{"class":117,"line":136},[115,11227,310],{"emptyLinePlaceholder":309},[115,11229,11230,11233,11236,11238],{"class":117,"line":149},[115,11231,11232],{"class":121},"with",[115,11234,11235],{"class":125}," connection.cursor() ",[115,11237,5719],{"class":121},[115,11239,11240],{"class":125}," cursor:\n",[115,11242,11243,11246,11249],{"class":117,"line":162},[115,11244,11245],{"class":125},"    cursor.execute(",[115,11247,11248],{"class":132},"\"SELECT current_database(), current_user;\"",[115,11250,2394],{"class":125},[115,11252,11253,11256],{"class":117,"line":175},[115,11254,11255],{"class":202},"    print",[115,11257,11258],{"class":125},"(cursor.fetchone())\n",[16,11260,11261],{},"If needed, also confirm that the configured host resolves from inside the running app container or VM and that the process has loaded the new secret values after restart or redeploy.",[16,11263,11264],{},"If you have a safe noncritical model or a staging environment, test a simple read and write path there before production cutover.",[16,11266,7697],{},[63,11268,11269,11272,11275,11278,11281],{},[66,11270,11271],{},"Django starts with the new settings",[66,11273,11274],{},"a simple SQL query succeeds",[66,11276,11277],{},"the query runs against the expected database",[66,11279,11280],{},"SSL is enabled for the session",[66,11282,11283],{},"the application process is using the updated secret values",[16,11285,11286],{},"Do not run migrations until these checks pass.",[11,11288,11290],{"id":11289},"_8-apply-migrations-safely-in-production","8) Apply migrations safely in production",[16,11292,11293],{},"Before changing schema:",[1173,11295,11296,11299,11302,11305],{},[66,11297,11298],{},"take a provider snapshot or manual backup",[66,11300,11301],{},"export or version the current secret\u002Fconfig values",[66,11303,11304],{},"confirm the app is pointed at the managed database you intend to migrate",[66,11306,11307],{},"confirm whether this target already contains restored application data or is a fresh database",[16,11309,11310],{},"Then run:",[106,11312,11314],{"className":108,"code":11313,"language":110,"meta":111,"style":111},"python manage.py migrate\n",[20,11315,11316],{"__ignoreMap":111},[115,11317,11318,11320,11322],{"class":117,"line":118},[115,11319,1114],{"class":262},[115,11321,1117],{"class":132},[115,11323,11324],{"class":132}," migrate\n",[16,11326,11327],{},"After migration, check:",[106,11329,11331],{"className":108,"code":11330,"language":110,"meta":111,"style":111},"python manage.py showmigrations\n",[20,11332,11333],{"__ignoreMap":111},[115,11334,11335,11337,11339],{"class":117,"line":118},[115,11336,1114],{"class":262},[115,11338,1117],{"class":132},[115,11340,1129],{"class":132},[16,11342,11343],{},"Minimal rollback rule:",[63,11345,11346,11349],{},[66,11347,11348],{},"if the app has not started serving production writes to the new database, rollback can usually be a config revert plus redeploy",[66,11350,11351],{},"if production writes have already started on the new database, rollback is a recovery decision and may require data reconciliation",[16,11353,11354],{},"If a migration fails, do not continue with partial cutover. Stop and assess:",[63,11356,11357,11360,11363],{},[66,11358,11359],{},"did the failure happen before any schema changes?",[66,11361,11362],{},"were some migrations applied?",[66,11364,11365],{},"is the app already sending writes to the new database?",[16,11367,11368],{},"A backup is not the same as an instant rollback. Once writes diverge between old and new databases, reverting safely becomes much harder.",[11,11370,11372],{"id":11371},"_9-verify-the-application-after-deployment","9) Verify the application after deployment",[16,11374,11375],{},"After the deploy:",[63,11377,11378,11381,11384,11387,11390],{},[66,11379,11380],{},"check application logs for connection errors",[66,11382,11383],{},"hit health endpoints",[66,11385,11386],{},"confirm normal page loads or API responses",[66,11388,11389],{},"test at least one read and one write path",[66,11391,11392],{},"watch provider session counts and connection limits",[16,11394,11395],{},"If you use PgBouncer or another pooler, confirm your connection settings are compatible with it. Managed databases often have lower connection limits than self-hosted installs, so verify that Gunicorn worker count, Django connection reuse, and pooler settings make sense together.",[16,11397,11398],{},"Keep the previous database secret values available until you are confident the new deployment is stable.",[11,11400,1321],{"id":1320},[16,11402,11403],{},"This setup works because it separates concerns cleanly:",[63,11405,11406,11409,11412,11415,11418,11421],{},[66,11407,11408],{},"secrets are injected at runtime rather than stored in code",[66,11410,11411],{},"PostgreSQL settings are explicit and reproducible",[66,11413,11414],{},"TLS is enforced at the client level",[66,11416,11417],{},"network restrictions reduce exposure if credentials leak",[66,11419,11420],{},"migrations happen only after connectivity is validated",[66,11422,11423],{},"rollback remains possible because previous settings are preserved and traffic is not switched too early",[16,11425,11426,11427,11429,11430,11432],{},"Use explicit ",[20,11428,10632],{}," settings when you want clarity and easier per-option control. Use ",[20,11431,10873],{}," when your platform standardizes on connection strings. Both are valid if secrets are handled correctly and SSL options are present.",[52,11434,11436],{"id":11435},"when-to-turn-this-into-a-reusable-script-or-template","When to turn this into a reusable script or template",[16,11438,11439],{},"If you deploy multiple environments or repeat this process often, the manual steps become easy to miss. Good candidates for automation are secret validation, SSL option checks, pre-migration connectivity tests, backup reminders, and post-deploy smoke tests. A reusable settings template and CI\u002FCD preflight step usually provide the biggest reliability gain first.",[11,11441,11443],{"id":11442},"notes-and-edge-cases","Notes and edge cases",[63,11445,11446,11452,11463,11469,11475,11481,11494],{},[66,11447,11448,11451],{},[1226,11449,11450],{},"PgBouncer or provider poolers:"," transaction pooling can affect features like server-side prepared statements and long-lived connections. Test with your exact provider mode.",[66,11453,11454,11458,11459,11462],{},[1226,11455,11456,241],{},[20,11457,10933],{}," a small nonzero value such as ",[20,11460,11461],{},"60"," is reasonable for many apps, but review this if you use aggressive pooling or serverless runtimes.",[66,11464,11465,11468],{},[1226,11466,11467],{},"Containers and CI\u002FCD:"," secret injection differs between Docker Compose, Kubernetes, PaaS platforms, and VM-based deploys. Keep the Django settings the same; only the secret delivery mechanism should change.",[66,11470,11471,11474],{},[1226,11472,11473],{},"Process restarts:"," changing environment variables does nothing until the app process is restarted or the deployment is rolled out again.",[66,11476,11477,11480],{},[1226,11478,11479],{},"Multiple environments:"," use separate databases and separate credentials for staging and production. Do not share one managed database unless you are intentionally isolating by schema and understand the risk.",[66,11482,11483,11486,11487,3146,11490,11493],{},[1226,11484,11485],{},"Client certificates:"," some providers or enterprise setups may require client certificate authentication in addition to CA verification. Follow provider documentation if ",[20,11488,11489],{},"sslcert",[20,11491,11492],{},"sslkey"," are required.",[66,11495,11496,11499],{},[1226,11497,11498],{},"Related production concerns:"," database cutover is only one part of deployment. You still need correct static files handling, reverse proxy configuration, HTTPS, and safe app server restarts.",[11,11501,1386],{"id":1385},[16,11503,11504,11505,211],{},"For the secret management side of this setup, see ",[1395,11506,11507],{"href":3006},"Django environment variables in production",[16,11509,11510,11511,211],{},"For a broader production readiness review, see ",[1395,11512,3000],{"href":2999},[16,11514,11515,11516,3146,11518,211],{},"For the web stack around your app, see ",[1395,11517,2986],{"href":2985},[1395,11519,8039],{"href":8038},[16,11521,11522,11523,211],{},"For migration workflow details, see ",[1395,11524,11525],{"href":1409},"run Django migrations safely in production",[16,11527,11528,11529,211],{},"If the app still cannot connect, use ",[1395,11530,11531],{"href":6333},"troubleshoot Django database connection errors in production",[11,11533,1420],{"id":1419},[52,11535,11537,11538,11540],{"id":11536},"do-i-need-sslmoderequire-for-managed-postgresql","Do I need ",[20,11539,10282],{}," for managed PostgreSQL?",[16,11542,11543,11544,11546,11547,11549],{},"Usually yes. For a production Django-to-PostgreSQL connection, ",[20,11545,10282],{}," is a practical minimum for encryption, though ",[20,11548,10286],{}," is stronger when supported.",[52,11551,11553,11554,3146,11556,4474],{"id":11552},"what-is-the-difference-between-require-and-verify-full","What is the difference between ",[20,11555,10928],{},[20,11557,10286],{},[16,11559,11560,11562,11563,11565],{},[20,11561,10928],{}," encrypts traffic between Django and PostgreSQL, but it does not verify the database server identity. ",[20,11564,10286],{}," encrypts traffic and verifies the certificate and hostname using the CA file you provide.",[52,11567,1434,11569,11571],{"id":11568},"should-i-use-database_url-or-explicit-django-settings",[20,11570,10873],{}," or explicit Django settings?",[16,11573,11574,11575,11577],{},"Either is fine. Explicit settings are easier to read and audit in Django. ",[20,11576,10873],{}," is convenient on platforms that already inject connection strings. The important part is that secrets stay out of Git and SSL options are not omitted.",[52,11579,11581],{"id":11580},"how-do-i-rotate-database-credentials-without-downtime","How do I rotate database credentials without downtime?",[16,11583,11584],{},"Create or stage the new credential in your provider first, update the runtime secret, and redeploy so new connections use it. Keep the old credential valid briefly if your provider supports overlap. Then remove the old credential after you confirm the app is stable.",[52,11586,11588],{"id":11587},"can-i-point-staging-and-production-to-the-same-managed-database","Can I point staging and production to the same managed database?",[16,11590,11591],{},"No, not as a normal practice. Use separate databases and separate credentials. Sharing a database increases the risk of accidental schema changes, test data contamination, and production outages.",[1485,11593,11594],{},"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 .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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":11596},[11597,11598,11599,11600,11601,11602,11603,11611,11612,11613,11614,11615,11616,11619,11620,11621],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":10310,"depth":136,"text":10311},{"id":10372,"depth":136,"text":10373},{"id":10535,"depth":136,"text":10536},{"id":10624,"depth":136,"text":10625,"children":11604},[11605,11607,11609,11610],{"id":10628,"depth":149,"text":11606},"Example using explicit DATABASES settings",{"id":10869,"depth":149,"text":11608},"Example using DATABASE_URL",{"id":10879,"depth":149,"text":10880},{"id":10904,"depth":149,"text":10905},{"id":10956,"depth":136,"text":10957},{"id":11118,"depth":136,"text":11119},{"id":11172,"depth":136,"text":11173},{"id":11289,"depth":136,"text":11290},{"id":11371,"depth":136,"text":11372},{"id":1320,"depth":136,"text":1321,"children":11617},[11618],{"id":11435,"depth":149,"text":11436},{"id":11442,"depth":136,"text":11443},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":11622},[11623,11625,11627,11629,11630],{"id":11536,"depth":149,"text":11624},"Do I need sslmode=require for managed PostgreSQL?",{"id":11552,"depth":149,"text":11626},"What is the difference between require and verify-full?",{"id":11568,"depth":149,"text":11628},"Should I use DATABASE_URL or explicit Django settings?",{"id":11580,"depth":149,"text":11581},{"id":11587,"depth":149,"text":11588},"Moving a Django app from a local database or self-managed PostgreSQL server to a managed PostgreSQL service sounds simple: update the host, username, and password, then deploy.",{},"\u002Fconfigure-managed-postgres-for-django","21",[2992,11636,11637],"\u002Fdeploy\u002Fdeploy-django-on-fly-io","\u002Fdeploy\u002Fdeploy-django-on-render",{"title":10250,"description":11631},[1557,1558,3101],"configure-managed-postgres-for-django",[1557,1558,3101],"qZZH5xXbDnOHpJ2fb_eOjCdMvps8rYCHW4qODxFTTjM",{"id":11644,"title":8039,"body":11645,"category":3088,"description":14022,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":14023,"navigation":309,"path":14024,"priority":14025,"related":14026,"role":1553,"section":3098,"seo":14028,"stack":14029,"stem":14030,"tags":14031,"type":1561,"__hash__":14032},"articles\u002Fdeploy-django-uvicorn-nginx.md",{"type":8,"value":11646,"toc":13975},[11647,11649,11659,11662,11685,11695,11697,11704,11737,11740,11742,11746,11750,11757,11812,11815,11829,11832,11836,11839,12009,12012,12026,12035,12038,12156,12159,12173,12177,12180,12203,12205,12220,12223,12227,12234,12298,12305,12354,12361,12376,12379,12383,12386,12412,12415,12455,12458,12486,12492,12496,12500,12503,12507,12512,12626,12632,12634,12683,12686,12722,12725,12745,12747,12764,12767,12779,12783,12787,12792,12977,12980,12992,12996,13006,13009,13034,13043,13080,13090,13094,13132,13134,13156,13163,13196,13200,13203,13223,13226,13247,13250,13260,13263,13283,13286,13297,13300,13303,13323,13330,13334,13337,13352,13355,13373,13376,13389,13392,13406,13413,13427,13430,13444,13447,13493,13503,13507,13510,13609,13612,13634,13637,13649,13656,13659,13676,13679,13681,13691,13693,13697,13700,13704,13707,13723,13726,13730,13733,13746,13748,13752,13755,13788,13791,13795,13802,13816,13826,13830,13833,13850,13854,13857,13870,13874,13876,13900,13902,13905,13932,13934,13938,13944,13948,13951,13955,13958,13962,13965,13969,13972],[11,11648,14],{"id":13},[16,11650,11651,11652,1153,11655,11658],{},"If you want to ",[1226,11653,11654],{},"deploy Django ASGI with Uvicorn and Nginx",[20,11656,11657],{},"python manage.py runserver"," is not a production option. It does not provide process supervision, safe restarts, TLS termination, or reliable reverse proxy behavior.",[16,11660,11661],{},"For a real Django ASGI deployment, you need a few pieces working together:",[63,11663,11664,11670,11673,11676,11679,11682],{},[66,11665,11666,11667],{},"an ASGI application entrypoint such as ",[20,11668,11669],{},"project.asgi:application",[66,11671,11672],{},"a production ASGI server such as Uvicorn",[66,11674,11675],{},"a reverse proxy such as Nginx",[66,11677,11678],{},"static file handling outside Django",[66,11680,11681],{},"environment-based secrets and production settings",[66,11683,11684],{},"a restart and rollback path that does not leave the app half-deployed",[16,11686,11687,11688,11691,11692,11694],{},"This guide shows a practical single-server setup: Django ASGI running under ",[1226,11689,11690],{},"Uvicorn + systemd",", with ",[1226,11693,1647],{}," in front for proxying, static files, and TLS.",[11,11696,30],{"id":29},[16,11698,11699,11700,11703],{},"The standard pattern to ",[1226,11701,11702],{},"run Django with Uvicorn and Nginx in production"," is:",[1173,11705,11706,11712,11715,11721,11724,11731,11734],{},[66,11707,11708,11709,11711],{},"configure Django for production with ",[20,11710,2707],{},", correct hosts, static paths, and proxy SSL settings",[66,11713,11714],{},"install your app into a virtualenv on the server",[66,11716,11717,11718,11720],{},"run Uvicorn as a ",[20,11719,1277],{}," service",[66,11722,11723],{},"proxy requests from Nginx to Uvicorn over a Unix socket or local TCP port",[66,11725,11726,11727,11730],{},"serve ",[20,11728,11729],{},"\u002Fstatic\u002F"," directly from Nginx",[66,11732,11733],{},"add HTTPS with Let's Encrypt",[66,11735,11736],{},"verify health before sending traffic",[16,11738,11739],{},"This article uses a manual single-server setup first. That keeps the moving parts visible and makes later automation easier.",[11,11741,43],{"id":42},[11,11743,11745],{"id":11744},"step-1-prepare-django-for-production","Step 1 — Prepare Django for production",[52,11747,11749],{"id":11748},"confirm-the-asgi-entrypoint","Confirm the ASGI entrypoint",[16,11751,11752,11753,11756],{},"Make sure your project has a working ",[20,11754,11755],{},"asgi.py",", usually:",[106,11758,11760],{"className":2369,"code":11759,"language":1114,"meta":111,"style":111},"# project\u002Fasgi.py\nimport os\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"project.settings\")\napplication = get_asgi_application()\n",[20,11761,11762,11767,11773,11785,11789,11802],{"__ignoreMap":111},[115,11763,11764],{"class":117,"line":118},[115,11765,11766],{"class":3861},"# project\u002Fasgi.py\n",[115,11768,11769,11771],{"class":117,"line":136},[115,11770,5613],{"class":121},[115,11772,5616],{"class":125},[115,11774,11775,11777,11780,11782],{"class":117,"line":149},[115,11776,5621],{"class":121},[115,11778,11779],{"class":125}," django.core.asgi ",[115,11781,5613],{"class":121},[115,11783,11784],{"class":125}," get_asgi_application\n",[115,11786,11787],{"class":117,"line":162},[115,11788,310],{"emptyLinePlaceholder":309},[115,11790,11791,11793,11795,11797,11800],{"class":117,"line":175},[115,11792,5638],{"class":125},[115,11794,5641],{"class":132},[115,11796,1153],{"class":125},[115,11798,11799],{"class":132},"\"project.settings\"",[115,11801,2394],{"class":125},[115,11803,11804,11807,11809],{"class":117,"line":350},[115,11805,11806],{"class":125},"application ",[115,11808,129],{"class":121},[115,11810,11811],{"class":125}," get_asgi_application()\n",[16,11813,11814],{},"Test that the import works from your project root:",[106,11816,11818],{"className":108,"code":11817,"language":110,"meta":111,"style":111},"python -c \"from project.asgi import application; print(application)\"\n",[20,11819,11820],{"__ignoreMap":111},[115,11821,11822,11824,11826],{"class":117,"line":118},[115,11823,1114],{"class":262},[115,11825,1024],{"class":202},[115,11827,11828],{"class":132}," \"from project.asgi import application; print(application)\"\n",[16,11830,11831],{},"If you are only serving normal HTTP requests, plain Django ASGI is enough. If you use WebSockets or Channels, the ASGI app and Nginx proxy config need additional handling.",[52,11833,11835],{"id":11834},"set-production-settings","Set production settings",[16,11837,11838],{},"Your production settings should at minimum include:",[106,11840,11842],{"className":2369,"code":11841,"language":1114,"meta":111,"style":111},"DEBUG = False\n\nALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\n\nCSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fexample.com\",\n    \"https:\u002F\u002Fwww.example.com\",\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\n# Enable after HTTPS and proxy headers are confirmed working\nSECURE_SSL_REDIRECT = True\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n",[20,11843,11844,11852,11856,11872,11876,11884,11890,11896,11900,11904,11914,11930,11934,11950,11958,11966,11970,11975,11983,11992,12000],{"__ignoreMap":111},[115,11845,11846,11848,11850],{"class":117,"line":118},[115,11847,7350],{"class":202},[115,11849,2380],{"class":121},[115,11851,7355],{"class":202},[115,11853,11854],{"class":117,"line":136},[115,11855,310],{"emptyLinePlaceholder":309},[115,11857,11858,11860,11862,11864,11866,11868,11870],{"class":117,"line":149},[115,11859,2719],{"class":202},[115,11861,2380],{"class":121},[115,11863,7493],{"class":125},[115,11865,7496],{"class":132},[115,11867,1153],{"class":125},[115,11869,7501],{"class":132},[115,11871,2552],{"class":125},[115,11873,11874],{"class":117,"line":162},[115,11875,310],{"emptyLinePlaceholder":309},[115,11877,11878,11880,11882],{"class":117,"line":175},[115,11879,2725],{"class":202},[115,11881,2380],{"class":121},[115,11883,3540],{"class":125},[115,11885,11886,11888],{"class":117,"line":350},[115,11887,3582],{"class":132},[115,11889,3354],{"class":125},[115,11891,11892,11894],{"class":117,"line":365},[115,11893,3589],{"class":132},[115,11895,3354],{"class":125},[115,11897,11898],{"class":117,"line":380},[115,11899,2552],{"class":125},[115,11901,11902],{"class":117,"line":487},[115,11903,310],{"emptyLinePlaceholder":309},[115,11905,11906,11909,11911],{"class":117,"line":2095},[115,11907,11908],{"class":202},"STATIC_URL",[115,11910,2380],{"class":121},[115,11912,11913],{"class":132}," \"\u002Fstatic\u002F\"\n",[115,11915,11916,11919,11921,11924,11927],{"class":117,"line":2104},[115,11917,11918],{"class":202},"STATIC_ROOT",[115,11920,2380],{"class":121},[115,11922,11923],{"class":202}," BASE_DIR",[115,11925,11926],{"class":121}," \u002F",[115,11928,11929],{"class":132}," \"staticfiles\"\n",[115,11931,11932],{"class":117,"line":2113},[115,11933,310],{"emptyLinePlaceholder":309},[115,11935,11936,11938,11940,11942,11944,11946,11948],{"class":117,"line":2122},[115,11937,2377],{"class":202},[115,11939,2380],{"class":121},[115,11941,2383],{"class":125},[115,11943,2386],{"class":132},[115,11945,1153],{"class":125},[115,11947,2391],{"class":132},[115,11949,2394],{"class":125},[115,11951,11952,11954,11956],{"class":117,"line":2131},[115,11953,2417],{"class":202},[115,11955,2380],{"class":121},[115,11957,2412],{"class":202},[115,11959,11960,11962,11964],{"class":117,"line":2136},[115,11961,2426],{"class":202},[115,11963,2380],{"class":121},[115,11965,2412],{"class":202},[115,11967,11968],{"class":117,"line":2142},[115,11969,310],{"emptyLinePlaceholder":309},[115,11971,11972],{"class":117,"line":2273},[115,11973,11974],{"class":3861},"# Enable after HTTPS and proxy headers are confirmed working\n",[115,11976,11977,11979,11981],{"class":117,"line":2282},[115,11978,2407],{"class":202},[115,11980,2380],{"class":121},[115,11982,2412],{"class":202},[115,11984,11985,11987,11989],{"class":117,"line":2291},[115,11986,7440],{"class":202},[115,11988,2380],{"class":121},[115,11990,11991],{"class":202}," 31536000\n",[115,11993,11994,11996,11998],{"class":117,"line":2299},[115,11995,7464],{"class":202},[115,11997,2380],{"class":121},[115,11999,2412],{"class":202},[115,12001,12002,12005,12007],{"class":117,"line":2307},[115,12003,12004],{"class":202},"SECURE_HSTS_PRELOAD",[115,12006,2380],{"class":121},[115,12008,7355],{"class":202},[16,12010,12011],{},"If Django should trust the host forwarded by Nginx, add:",[106,12013,12015],{"className":2369,"code":12014,"language":1114,"meta":111,"style":111},"USE_X_FORWARDED_HOST = True\n",[20,12016,12017],{"__ignoreMap":111},[115,12018,12019,12022,12024],{"class":117,"line":118},[115,12020,12021],{"class":202},"USE_X_FORWARDED_HOST",[115,12023,2380],{"class":121},[115,12025,2412],{"class":202},[16,12027,12028,12029,12031,12032,12034],{},"Only use ",[20,12030,12021],{}," if you intend Django to rely on the reverse proxy host header. In many simple setups, ",[20,12033,3648],{}," forwarding from Nginx is already enough.",[16,12036,12037],{},"Load secrets and database settings from environment variables, not from the repo:",[106,12039,12041],{"className":2369,"code":12040,"language":1114,"meta":111,"style":111},"import os\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": os.environ[\"DB_NAME\"],\n        \"USER\": os.environ[\"DB_USER\"],\n        \"PASSWORD\": os.environ[\"DB_PASSWORD\"],\n        \"HOST\": os.environ[\"DB_HOST\"],\n        \"PORT\": os.environ.get(\"DB_PORT\", \"5432\"),\n    }\n}\n",[20,12042,12043,12049,12053,12066,12070,12078,12084,12094,12104,12114,12124,12134,12148,12152],{"__ignoreMap":111},[115,12044,12045,12047],{"class":117,"line":118},[115,12046,5613],{"class":121},[115,12048,5616],{"class":125},[115,12050,12051],{"class":117,"line":136},[115,12052,310],{"emptyLinePlaceholder":309},[115,12054,12055,12057,12059,12061,12064],{"class":117,"line":149},[115,12056,2713],{"class":202},[115,12058,2380],{"class":121},[115,12060,8861],{"class":125},[115,12062,12063],{"class":132},"\"DJANGO_SECRET_KEY\"",[115,12065,2552],{"class":125},[115,12067,12068],{"class":117,"line":162},[115,12069,310],{"emptyLinePlaceholder":309},[115,12071,12072,12074,12076],{"class":117,"line":175},[115,12073,10632],{"class":202},[115,12075,2380],{"class":121},[115,12077,2166],{"class":125},[115,12079,12080,12082],{"class":117,"line":350},[115,12081,10664],{"class":132},[115,12083,3374],{"class":125},[115,12085,12086,12088,12090,12092],{"class":117,"line":365},[115,12087,10671],{"class":132},[115,12089,2513],{"class":125},[115,12091,10676],{"class":132},[115,12093,3354],{"class":125},[115,12095,12096,12098,12100,12102],{"class":117,"line":380},[115,12097,10683],{"class":132},[115,12099,10686],{"class":125},[115,12101,10689],{"class":132},[115,12103,3430],{"class":125},[115,12105,12106,12108,12110,12112],{"class":117,"line":487},[115,12107,10696],{"class":132},[115,12109,10686],{"class":125},[115,12111,10701],{"class":132},[115,12113,3430],{"class":125},[115,12115,12116,12118,12120,12122],{"class":117,"line":2095},[115,12117,10708],{"class":132},[115,12119,10686],{"class":125},[115,12121,10713],{"class":132},[115,12123,3430],{"class":125},[115,12125,12126,12128,12130,12132],{"class":117,"line":2104},[115,12127,10720],{"class":132},[115,12129,10686],{"class":125},[115,12131,10725],{"class":132},[115,12133,3430],{"class":125},[115,12135,12136,12138,12140,12142,12144,12146],{"class":117,"line":2113},[115,12137,10732],{"class":132},[115,12139,10735],{"class":125},[115,12141,10738],{"class":132},[115,12143,1153],{"class":125},[115,12145,10743],{"class":132},[115,12147,10746],{"class":125},[115,12149,12150],{"class":117,"line":2122},[115,12151,2233],{"class":125},[115,12153,12154],{"class":117,"line":2131},[115,12155,2323],{"class":125},[16,12157,12158],{},"Run Django’s deployment checks before going further:",[106,12160,12161],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,12162,12163],{"__ignoreMap":111},[115,12164,12165,12167,12169,12171],{"class":117,"line":118},[115,12166,1114],{"class":262},[115,12168,1117],{"class":132},[115,12170,1814],{"class":132},[115,12172,1817],{"class":202},[52,12174,12176],{"id":12175},"collect-static-files-and-run-migrations","Collect static files and run migrations",[16,12178,12179],{},"After dependencies are installed and environment variables are available:",[106,12181,12183],{"className":108,"code":12182,"language":110,"meta":111,"style":111},"python manage.py migrate\npython manage.py collectstatic --noinput\n",[20,12184,12185,12193],{"__ignoreMap":111},[115,12186,12187,12189,12191],{"class":117,"line":118},[115,12188,1114],{"class":262},[115,12190,1117],{"class":132},[115,12192,11324],{"class":132},[115,12194,12195,12197,12199,12201],{"class":117,"line":136},[115,12196,1114],{"class":262},[115,12198,1117],{"class":132},[115,12200,1838],{"class":132},[115,12202,1841],{"class":202},[16,12204,8572],{},[106,12206,12208],{"className":108,"code":12207,"language":110,"meta":111,"style":111},"ls -lah staticfiles\u002F\n",[20,12209,12210],{"__ignoreMap":111},[115,12211,12212,12214,12217],{"class":117,"line":118},[115,12213,532],{"class":262},[115,12215,12216],{"class":202}," -lah",[115,12218,12219],{"class":132}," staticfiles\u002F\n",[16,12221,12222],{},"Rollback note: migrations are often the riskiest part of a deployment. If a migration is backward-incompatible, do not assume you can safely roll back only the code.",[11,12224,12226],{"id":12225},"step-2-create-the-application-environment-on-the-server","Step 2 — Create the application environment on the server",[16,12228,12229,12230,12233],{},"This example uses ",[20,12231,12232],{},"\u002Fsrv\u002Fproject"," with a simple layout.",[106,12235,12237],{"className":108,"code":12236,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fsrv\u002Fproject\nsudo chown $USER:$USER \u002Fsrv\u002Fproject\ncd \u002Fsrv\u002Fproject\n\npython3 -m venv .venv\nsource .venv\u002Fbin\u002Factivate\n",[20,12238,12239,12250,12267,12273,12277,12291],{"__ignoreMap":111},[115,12240,12241,12243,12245,12247],{"class":117,"line":118},[115,12242,2001],{"class":262},[115,12244,6721],{"class":132},[115,12246,1001],{"class":202},[115,12248,12249],{"class":132}," \u002Fsrv\u002Fproject\n",[115,12251,12252,12254,12256,12259,12261,12264],{"class":117,"line":136},[115,12253,2001],{"class":262},[115,12255,6733],{"class":132},[115,12257,12258],{"class":125}," $USER",[115,12260,241],{"class":132},[115,12262,12263],{"class":125},"$USER ",[115,12265,12266],{"class":132},"\u002Fsrv\u002Fproject\n",[115,12268,12269,12271],{"class":117,"line":149},[115,12270,5303],{"class":202},[115,12272,12249],{"class":132},[115,12274,12275],{"class":117,"line":162},[115,12276,310],{"emptyLinePlaceholder":309},[115,12278,12279,12282,12285,12288],{"class":117,"line":175},[115,12280,12281],{"class":262},"python3",[115,12283,12284],{"class":202}," -m",[115,12286,12287],{"class":132}," venv",[115,12289,12290],{"class":132}," .venv\n",[115,12292,12293,12295],{"class":117,"line":350},[115,12294,5311],{"class":202},[115,12296,12297],{"class":132}," .venv\u002Fbin\u002Factivate\n",[16,12299,12300,12301,12304],{},"Copy or clone your application code into ",[20,12302,12303],{},"\u002Fsrv\u002Fproject\u002Fapp",", then install dependencies:",[106,12306,12308],{"className":108,"code":12307,"language":110,"meta":111,"style":111},"mkdir -p \u002Fsrv\u002Fproject\u002Fapp\ncd \u002Fsrv\u002Fproject\u002Fapp\n\npip install --upgrade pip\npip install -r requirements.txt\n",[20,12309,12310,12320,12326,12330,12342],{"__ignoreMap":111},[115,12311,12312,12315,12317],{"class":117,"line":118},[115,12313,12314],{"class":262},"mkdir",[115,12316,1001],{"class":202},[115,12318,12319],{"class":132}," \u002Fsrv\u002Fproject\u002Fapp\n",[115,12321,12322,12324],{"class":117,"line":136},[115,12323,5303],{"class":202},[115,12325,12319],{"class":132},[115,12327,12328],{"class":117,"line":149},[115,12329,310],{"emptyLinePlaceholder":309},[115,12331,12332,12334,12336,12339],{"class":117,"line":162},[115,12333,8618],{"class":262},[115,12335,6600],{"class":132},[115,12337,12338],{"class":202}," --upgrade",[115,12340,12341],{"class":132}," pip\n",[115,12343,12344,12346,12348,12351],{"class":117,"line":175},[115,12345,8618],{"class":262},[115,12347,6600],{"class":132},[115,12349,12350],{"class":202}," -r",[115,12352,12353],{"class":132}," requirements.txt\n",[16,12355,12356,12357,12360],{},"Make sure ",[20,12358,12359],{},"uvicorn"," is installed:",[106,12362,12364],{"className":108,"code":12363,"language":110,"meta":111,"style":111},"pip show uvicorn\n",[20,12365,12366],{"__ignoreMap":111},[115,12367,12368,12370,12373],{"class":117,"line":118},[115,12369,8618],{"class":262},[115,12371,12372],{"class":132}," show",[115,12374,12375],{"class":132}," uvicorn\n",[16,12377,12378],{},"For this stack, Gunicorn is not required. Use it only if you intentionally want Gunicorn managing Uvicorn workers.",[52,12380,12382],{"id":12381},"store-secrets-safely","Store secrets safely",[16,12384,12385],{},"Create an environment file owned by root and readable by the app service user:",[106,12387,12389],{"className":108,"code":12388,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fetc\u002Fproject\nsudo nano \u002Fetc\u002Fproject\u002Fproject.env\n",[20,12390,12391,12402],{"__ignoreMap":111},[115,12392,12393,12395,12397,12399],{"class":117,"line":118},[115,12394,2001],{"class":262},[115,12396,6721],{"class":132},[115,12398,1001],{"class":202},[115,12400,12401],{"class":132}," \u002Fetc\u002Fproject\n",[115,12403,12404,12406,12409],{"class":117,"line":136},[115,12405,2001],{"class":262},[115,12407,12408],{"class":132}," nano",[115,12410,12411],{"class":132}," \u002Fetc\u002Fproject\u002Fproject.env\n",[16,12413,12414],{},"Example:",[106,12416,12418],{"className":2329,"code":12417,"language":2331,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=project.settings\nDJANGO_SECRET_KEY=replace-me\nDB_NAME=projectdb\nDB_USER=projectuser\nDB_PASSWORD=replace-me\nDB_HOST=127.0.0.1\nDB_PORT=5432\n",[20,12419,12420,12425,12430,12435,12440,12445,12450],{"__ignoreMap":111},[115,12421,12422],{"class":117,"line":118},[115,12423,12424],{},"DJANGO_SETTINGS_MODULE=project.settings\n",[115,12426,12427],{"class":117,"line":136},[115,12428,12429],{},"DJANGO_SECRET_KEY=replace-me\n",[115,12431,12432],{"class":117,"line":149},[115,12433,12434],{},"DB_NAME=projectdb\n",[115,12436,12437],{"class":117,"line":162},[115,12438,12439],{},"DB_USER=projectuser\n",[115,12441,12442],{"class":117,"line":175},[115,12443,12444],{},"DB_PASSWORD=replace-me\n",[115,12446,12447],{"class":117,"line":350},[115,12448,12449],{},"DB_HOST=127.0.0.1\n",[115,12451,12452],{"class":117,"line":365},[115,12453,12454],{},"DB_PORT=5432\n",[16,12456,12457],{},"Set permissions:",[106,12459,12461],{"className":108,"code":12460,"language":110,"meta":111,"style":111},"sudo chown root:www-data \u002Fetc\u002Fproject\u002Fproject.env\nsudo chmod 640 \u002Fetc\u002Fproject\u002Fproject.env\n",[20,12462,12463,12474],{"__ignoreMap":111},[115,12464,12465,12467,12469,12472],{"class":117,"line":118},[115,12466,2001],{"class":262},[115,12468,6733],{"class":132},[115,12470,12471],{"class":132}," root:www-data",[115,12473,12411],{"class":132},[115,12475,12476,12478,12481,12484],{"class":117,"line":136},[115,12477,2001],{"class":262},[115,12479,12480],{"class":132}," chmod",[115,12482,12483],{"class":202}," 640",[115,12485,12411],{"class":132},[16,12487,12488,12489,12491],{},"Do not put secrets in the Git repo or directly in the ",[20,12490,1277],{}," unit.",[11,12493,12495],{"id":12494},"step-3-run-uvicorn-with-systemd","Step 3 — Run Uvicorn with systemd",[52,12497,12499],{"id":12498},"choose-bind-method-unix-socket-vs-tcp-port","Choose bind method: Unix socket vs TCP port",[16,12501,12502],{},"For local Nginx-to-Uvicorn proxying on one server, a Unix socket is usually a good default. It avoids exposing an app port beyond localhost and fits this architecture well.",[52,12504,12506],{"id":12505},"create-the-systemd-service","Create the systemd service",[16,12508,8628,12509,241],{},[20,12510,12511],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fproject-uvicorn.service",[106,12513,12515],{"className":2026,"code":12514,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Uvicorn service for Django project\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fproject\u002Fapp\nEnvironmentFile=\u002Fetc\u002Fproject\u002Fproject.env\nRuntimeDirectory=uvicorn\nRuntimeDirectoryMode=775\nUMask=007\nExecStart=\u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Fuvicorn project.asgi:application --workers 2 --uds \u002Frun\u002Fuvicorn\u002Fproject.sock\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\n",[20,12516,12517,12521,12528,12534,12538,12542,12549,12555,12562,12569,12576,12584,12592,12599,12605,12612,12616,12620],{"__ignoreMap":111},[115,12518,12519],{"class":117,"line":118},[115,12520,2035],{"class":262},[115,12522,12523,12525],{"class":117,"line":136},[115,12524,2040],{"class":121},[115,12526,12527],{"class":125},"=Uvicorn service for Django project\n",[115,12529,12530,12532],{"class":117,"line":149},[115,12531,2048],{"class":121},[115,12533,2051],{"class":125},[115,12535,12536],{"class":117,"line":162},[115,12537,310],{"emptyLinePlaceholder":309},[115,12539,12540],{"class":117,"line":175},[115,12541,2060],{"class":262},[115,12543,12544,12546],{"class":117,"line":350},[115,12545,2065],{"class":121},[115,12547,12548],{"class":125},"=deploy\n",[115,12550,12551,12553],{"class":117,"line":365},[115,12552,2073],{"class":121},[115,12554,2076],{"class":125},[115,12556,12557,12559],{"class":117,"line":380},[115,12558,2081],{"class":121},[115,12560,12561],{"class":125},"=\u002Fsrv\u002Fproject\u002Fapp\n",[115,12563,12564,12566],{"class":117,"line":487},[115,12565,2089],{"class":121},[115,12567,12568],{"class":125},"=\u002Fetc\u002Fproject\u002Fproject.env\n",[115,12570,12571,12573],{"class":117,"line":2095},[115,12572,2098],{"class":121},[115,12574,12575],{"class":125},"=uvicorn\n",[115,12577,12578,12581],{"class":117,"line":2104},[115,12579,12580],{"class":121},"RuntimeDirectoryMode",[115,12582,12583],{"class":125},"=775\n",[115,12585,12586,12589],{"class":117,"line":2113},[115,12587,12588],{"class":121},"UMask",[115,12590,12591],{"class":125},"=007\n",[115,12593,12594,12596],{"class":117,"line":2122},[115,12595,2107],{"class":121},[115,12597,12598],{"class":125},"=\u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Fuvicorn project.asgi:application --workers 2 --uds \u002Frun\u002Fuvicorn\u002Fproject.sock\n",[115,12600,12601,12603],{"class":117,"line":2131},[115,12602,2116],{"class":121},[115,12604,4932],{"class":125},[115,12606,12607,12609],{"class":117,"line":2136},[115,12608,2125],{"class":121},[115,12610,12611],{"class":125},"=3\n",[115,12613,12614],{"class":117,"line":2142},[115,12615,310],{"emptyLinePlaceholder":309},[115,12617,12618],{"class":117,"line":2273},[115,12619,2139],{"class":262},[115,12621,12622,12624],{"class":117,"line":2282},[115,12623,2145],{"class":121},[115,12625,2148],{"class":125},[16,12627,12628,12629,12631],{},"Replace ",[20,12630,3098],{}," with your actual deployment user.",[16,12633,8508],{},[63,12635,12636,12649,12662,12668,12674],{},[66,12637,12638,12641,12642,12644,12645,12648],{},[20,12639,12640],{},"RuntimeDirectory=uvicorn"," tells ",[20,12643,1277],{}," to create ",[20,12646,12647],{},"\u002Frun\u002Fuvicorn"," automatically on boot.",[66,12650,12651,3146,12654,12657,12658,12661],{},[20,12652,12653],{},"RuntimeDirectoryMode=775",[20,12655,12656],{},"UMask=007"," help make socket access predictable for Nginx when both processes share the ",[20,12659,12660],{},"www-data"," group.",[66,12663,12664,12667],{},[20,12665,12666],{},"--uds"," binds Uvicorn to a Unix domain socket.",[66,12669,12670,12673],{},[20,12671,12672],{},"--workers 2"," is a reasonable starting point for a small app. Tune based on CPU and workload.",[66,12675,12676,12678,12679,12682],{},[20,12677,2081],{}," should contain ",[20,12680,12681],{},"manage.py"," and your Django package.",[16,12684,12685],{},"Enable and start it:",[106,12687,12689],{"className":108,"code":12688,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable --now project-uvicorn\nsudo systemctl status project-uvicorn\n",[20,12690,12691,12699,12712],{"__ignoreMap":111},[115,12692,12693,12695,12697],{"class":117,"line":118},[115,12694,2001],{"class":262},[115,12696,3480],{"class":132},[115,12698,4984],{"class":132},[115,12700,12701,12703,12705,12707,12709],{"class":117,"line":136},[115,12702,2001],{"class":262},[115,12704,3480],{"class":132},[115,12706,8567],{"class":132},[115,12708,9433],{"class":202},[115,12710,12711],{"class":132}," project-uvicorn\n",[115,12713,12714,12716,12718,12720],{"class":117,"line":149},[115,12715,2001],{"class":262},[115,12717,3480],{"class":132},[115,12719,1984],{"class":132},[115,12721,12711],{"class":132},[16,12723,12724],{},"Check logs:",[106,12726,12728],{"className":108,"code":12727,"language":110,"meta":111,"style":111},"journalctl -u project-uvicorn -n 100 --no-pager\n",[20,12729,12730],{"__ignoreMap":111},[115,12731,12732,12734,12736,12739,12741,12743],{"class":117,"line":118},[115,12733,2785],{"class":262},[115,12735,2788],{"class":202},[115,12737,12738],{"class":132}," project-uvicorn",[115,12740,2794],{"class":202},[115,12742,2797],{"class":202},[115,12744,2800],{"class":202},[16,12746,8572],{},[106,12748,12750],{"className":108,"code":12749,"language":110,"meta":111,"style":111},"sudo ls -lah \u002Frun\u002Fuvicorn\u002Fproject.sock\n",[20,12751,12752],{"__ignoreMap":111},[115,12753,12754,12756,12759,12761],{"class":117,"line":118},[115,12755,2001],{"class":262},[115,12757,12758],{"class":132}," ls",[115,12760,12216],{"class":202},[115,12762,12763],{"class":132}," \u002Frun\u002Fuvicorn\u002Fproject.sock\n",[16,12765,12766],{},"If you prefer TCP instead, use:",[106,12768,12770],{"className":2026,"code":12769,"language":2028,"meta":111,"style":111},"ExecStart=\u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Fuvicorn project.asgi:application --workers 2 --host 127.0.0.1 --port 8000\n",[20,12771,12772],{"__ignoreMap":111},[115,12773,12774,12776],{"class":117,"line":118},[115,12775,2107],{"class":121},[115,12777,12778],{"class":125},"=\u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Fuvicorn project.asgi:application --workers 2 --host 127.0.0.1 --port 8000\n",[11,12780,12782],{"id":12781},"step-4-configure-nginx-as-the-reverse-proxy","Step 4 — Configure Nginx as the reverse proxy",[52,12784,12786],{"id":12785},"create-the-nginx-server-block","Create the Nginx server block",[16,12788,8628,12789,241],{},[20,12790,12791],{},"\u002Fetc\u002Fnginx\u002Fsites-available\u002Fproject",[106,12793,12795],{"className":2154,"code":12794,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    client_max_body_size 10M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fproject\u002Fapp\u002Fstaticfiles\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fuvicorn\u002Fproject.sock;\n        proxy_http_version 1.1;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_read_timeout 60;\n        proxy_connect_timeout 60;\n        proxy_redirect off;\n    }\n\n    location ~ \u002F\\. {\n        deny all;\n    }\n}\n",[20,12796,12797,12803,12811,12817,12821,12830,12834,12842,12849,12853,12857,12865,12872,12882,12888,12894,12900,12906,12912,12921,12930,12938,12942,12946,12959,12969,12973],{"__ignoreMap":111},[115,12798,12799,12801],{"class":117,"line":118},[115,12800,2163],{"class":121},[115,12802,2166],{"class":125},[115,12804,12805,12807,12809],{"class":117,"line":136},[115,12806,2171],{"class":121},[115,12808,3808],{"class":202},[115,12810,3811],{"class":125},[115,12812,12813,12815],{"class":117,"line":149},[115,12814,2182],{"class":121},[115,12816,3713],{"class":125},[115,12818,12819],{"class":117,"line":162},[115,12820,310],{"emptyLinePlaceholder":309},[115,12822,12823,12825,12828],{"class":117,"line":175},[115,12824,6987],{"class":121},[115,12826,12827],{"class":202},"10M",[115,12829,3811],{"class":125},[115,12831,12832],{"class":117,"line":350},[115,12833,310],{"emptyLinePlaceholder":309},[115,12835,12836,12838,12840],{"class":117,"line":365},[115,12837,2214],{"class":121},[115,12839,2217],{"class":262},[115,12841,2220],{"class":125},[115,12843,12844,12846],{"class":117,"line":380},[115,12845,2225],{"class":121},[115,12847,12848],{"class":125},"\u002Fsrv\u002Fproject\u002Fapp\u002Fstaticfiles\u002F;\n",[115,12850,12851],{"class":117,"line":487},[115,12852,2233],{"class":125},[115,12854,12855],{"class":117,"line":2095},[115,12856,310],{"emptyLinePlaceholder":309},[115,12858,12859,12861,12863],{"class":117,"line":2104},[115,12860,2214],{"class":121},[115,12862,2268],{"class":262},[115,12864,2220],{"class":125},[115,12866,12867,12869],{"class":117,"line":2113},[115,12868,2276],{"class":121},[115,12870,12871],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fuvicorn\u002Fproject.sock;\n",[115,12873,12874,12877,12880],{"class":117,"line":2122},[115,12875,12876],{"class":121},"        proxy_http_version ",[115,12878,12879],{"class":202},"1.1",[115,12881,3811],{"class":125},[115,12883,12884,12886],{"class":117,"line":2131},[115,12885,2285],{"class":121},[115,12887,2288],{"class":125},[115,12889,12890,12892],{"class":117,"line":2136},[115,12891,2285],{"class":121},[115,12893,2296],{"class":125},[115,12895,12896,12898],{"class":117,"line":2142},[115,12897,2285],{"class":121},[115,12899,3767],{"class":125},[115,12901,12902,12904],{"class":117,"line":2273},[115,12903,2285],{"class":121},[115,12905,2312],{"class":125},[115,12907,12908,12910],{"class":117,"line":2282},[115,12909,2285],{"class":121},[115,12911,2304],{"class":125},[115,12913,12914,12917,12919],{"class":117,"line":2291},[115,12915,12916],{"class":121},"        proxy_read_timeout ",[115,12918,11461],{"class":202},[115,12920,3811],{"class":125},[115,12922,12923,12926,12928],{"class":117,"line":2299},[115,12924,12925],{"class":121},"        proxy_connect_timeout ",[115,12927,11461],{"class":202},[115,12929,3811],{"class":125},[115,12931,12932,12934,12936],{"class":117,"line":2307},[115,12933,7100],{"class":121},[115,12935,7103],{"class":202},[115,12937,3811],{"class":125},[115,12939,12940],{"class":117,"line":2315},[115,12941,2233],{"class":125},[115,12943,12944],{"class":117,"line":2320},[115,12945,310],{"emptyLinePlaceholder":309},[115,12947,12948,12950,12953,12957],{"class":117,"line":7083},[115,12949,2214],{"class":121},[115,12951,12952],{"class":121}," ~",[115,12954,12956],{"class":12955},"sA_wV"," \u002F\\. ",[115,12958,2220],{"class":125},[115,12960,12961,12964,12967],{"class":117,"line":7090},[115,12962,12963],{"class":121},"        deny ",[115,12965,12966],{"class":202},"all",[115,12968,3811],{"class":125},[115,12970,12971],{"class":117,"line":7097},[115,12972,2233],{"class":125},[115,12974,12975],{"class":117,"line":7108},[115,12976,2323],{"class":125},[16,12978,12979],{},"If you use a TCP upstream instead:",[106,12981,12983],{"className":2154,"code":12982,"language":2156,"meta":111,"style":111},"proxy_pass http:\u002F\u002F127.0.0.1:8000;\n",[20,12984,12985],{"__ignoreMap":111},[115,12986,12987,12990],{"class":117,"line":118},[115,12988,12989],{"class":121},"proxy_pass ",[115,12991,3748],{"class":125},[52,12993,12995],{"id":12994},"serve-static-files-directly-with-nginx","Serve static files directly with Nginx",[16,12997,12998,12999,13002,13003,13005],{},"The ",[20,13000,13001],{},"alias"," path must match Django’s ",[20,13004,11918],{},". A common failure is pointing Nginx at the wrong directory or forgetting the trailing slash.",[16,13007,13008],{},"If you also serve media uploads:",[106,13010,13012],{"className":2154,"code":13011,"language":2156,"meta":111,"style":111},"location \u002Fmedia\u002F {\n    alias \u002Fsrv\u002Fproject\u002Fapp\u002Fmedia\u002F;\n}\n",[20,13013,13014,13022,13030],{"__ignoreMap":111},[115,13015,13016,13018,13020],{"class":117,"line":118},[115,13017,7128],{"class":121},[115,13019,2244],{"class":262},[115,13021,2220],{"class":125},[115,13023,13024,13027],{"class":117,"line":136},[115,13025,13026],{"class":121},"    alias ",[115,13028,13029],{"class":125},"\u002Fsrv\u002Fproject\u002Fapp\u002Fmedia\u002F;\n",[115,13031,13032],{"class":117,"line":149},[115,13033,2323],{"class":125},[16,13035,13036,13037,13039,13040,13042],{},"Make sure Nginx can read those directories. For example, if your app user is ",[20,13038,3098],{}," and Nginx uses the ",[20,13041,12660],{}," group:",[106,13044,13046],{"className":108,"code":13045,"language":110,"meta":111,"style":111},"sudo chown -R deploy:www-data \u002Fsrv\u002Fproject\u002Fapp\u002Fstaticfiles \u002Fsrv\u002Fproject\u002Fapp\u002Fmedia\nsudo chmod -R u=rwX,g=rX,o= \u002Fsrv\u002Fproject\u002Fapp\u002Fstaticfiles \u002Fsrv\u002Fproject\u002Fapp\u002Fmedia\n",[20,13047,13048,13065],{"__ignoreMap":111},[115,13049,13050,13052,13054,13056,13059,13062],{"class":117,"line":118},[115,13051,2001],{"class":262},[115,13053,6733],{"class":132},[115,13055,6736],{"class":202},[115,13057,13058],{"class":132}," deploy:www-data",[115,13060,13061],{"class":132}," \u002Fsrv\u002Fproject\u002Fapp\u002Fstaticfiles",[115,13063,13064],{"class":132}," \u002Fsrv\u002Fproject\u002Fapp\u002Fmedia\n",[115,13066,13067,13069,13071,13073,13076,13078],{"class":117,"line":136},[115,13068,2001],{"class":262},[115,13070,12480],{"class":132},[115,13072,6736],{"class":202},[115,13074,13075],{"class":132}," u=rwX,g=rX,o=",[115,13077,13061],{"class":132},[115,13079,13064],{"class":132},[16,13081,13082,13083,13086,13087,13089],{},"If your host uses a different Nginx user or group, adjust those commands. Be more careful with ",[20,13084,13085],{},"\u002Fmedia\u002F"," than ",[20,13088,11729],{},": uploaded files are user-controlled content and should not be treated like trusted application assets.",[52,13091,13093],{"id":13092},"enable-the-site-and-test-nginx-config","Enable the site and test Nginx config",[106,13095,13097],{"className":108,"code":13096,"language":110,"meta":111,"style":111},"sudo ln -s \u002Fetc\u002Fnginx\u002Fsites-available\u002Fproject \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fproject\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,13098,13099,13114,13122],{"__ignoreMap":111},[115,13100,13101,13103,13106,13108,13111],{"class":117,"line":118},[115,13102,2001],{"class":262},[115,13104,13105],{"class":132}," ln",[115,13107,549],{"class":202},[115,13109,13110],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fproject",[115,13112,13113],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fproject\n",[115,13115,13116,13118,13120],{"class":117,"line":136},[115,13117,2001],{"class":262},[115,13119,3906],{"class":132},[115,13121,4282],{"class":202},[115,13123,13124,13126,13128,13130],{"class":117,"line":149},[115,13125,2001],{"class":262},[115,13127,3480],{"class":132},[115,13129,3919],{"class":132},[115,13131,1996],{"class":132},[16,13133,8572],{},[106,13135,13137],{"className":108,"code":13136,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\ncurl -I http:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[20,13138,13139,13147],{"__ignoreMap":111},[115,13140,13141,13143,13145],{"class":117,"line":118},[115,13142,2764],{"class":262},[115,13144,2767],{"class":202},[115,13146,6494],{"class":132},[115,13148,13149,13151,13153],{"class":117,"line":136},[115,13150,2764],{"class":262},[115,13152,2767],{"class":202},[115,13154,13155],{"class":132}," http:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[16,13157,13158,13159,13162],{},"If Nginx returns ",[20,13160,13161],{},"502 Bad Gateway",", inspect both:",[106,13164,13166],{"className":108,"code":13165,"language":110,"meta":111,"style":111},"journalctl -u project-uvicorn -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,13167,13168,13182],{"__ignoreMap":111},[115,13169,13170,13172,13174,13176,13178,13180],{"class":117,"line":118},[115,13171,2785],{"class":262},[115,13173,2788],{"class":202},[115,13175,12738],{"class":132},[115,13177,2794],{"class":202},[115,13179,2797],{"class":202},[115,13181,2800],{"class":202},[115,13183,13184,13186,13189,13191,13193],{"class":117,"line":136},[115,13185,2001],{"class":262},[115,13187,13188],{"class":132}," tail",[115,13190,2794],{"class":202},[115,13192,2797],{"class":202},[115,13194,13195],{"class":132}," \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[11,13197,13199],{"id":13198},"step-5-add-tls-and-secure-the-edge","Step 5 — Add TLS and secure the edge",[16,13201,13202],{},"Install a certificate with Certbot using the Nginx plugin or your preferred ACME workflow:",[106,13204,13205],{"className":108,"code":6676,"language":110,"meta":111,"style":111},[20,13206,13207],{"__ignoreMap":111},[115,13208,13209,13211,13213,13215,13217,13219,13221],{"class":117,"line":118},[115,13210,2001],{"class":262},[115,13212,6603],{"class":132},[115,13214,6687],{"class":202},[115,13216,1019],{"class":202},[115,13218,6434],{"class":132},[115,13220,1019],{"class":202},[115,13222,6696],{"class":132},[16,13224,13225],{},"After issuance, verify redirect behavior:",[106,13227,13229],{"className":108,"code":13228,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\ncurl -I https:\u002F\u002Fexample.com\n",[20,13230,13231,13239],{"__ignoreMap":111},[115,13232,13233,13235,13237],{"class":117,"line":118},[115,13234,2764],{"class":262},[115,13236,2767],{"class":202},[115,13238,6494],{"class":132},[115,13240,13241,13243,13245],{"class":117,"line":136},[115,13242,2764],{"class":262},[115,13244,2767],{"class":202},[115,13246,2770],{"class":132},[16,13248,13249],{},"Django must receive the original HTTPS information through:",[106,13251,13252],{"className":2154,"code":8081,"language":2156,"meta":111,"style":111},[20,13253,13254],{"__ignoreMap":111},[115,13255,13256,13258],{"class":117,"line":118},[115,13257,8088],{"class":121},[115,13259,2304],{"class":125},[16,13261,13262],{},"And Django must trust that header:",[106,13264,13265],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,13266,13267],{"__ignoreMap":111},[115,13268,13269,13271,13273,13275,13277,13279,13281],{"class":117,"line":118},[115,13270,2377],{"class":202},[115,13272,2380],{"class":121},[115,13274,2383],{"class":125},[115,13276,2386],{"class":132},[115,13278,1153],{"class":125},[115,13280,2391],{"class":132},[115,13282,2394],{"class":125},[16,13284,13285],{},"Before considering the deployment complete, make sure one redirect layer is active:",[63,13287,13288,13291],{},[66,13289,13290],{},"either Nginx redirects HTTP to HTTPS",[66,13292,13293,13294],{},"or Django uses ",[20,13295,13296],{},"SECURE_SSL_REDIRECT = True",[16,13298,13299],{},"Do not leave both HTTP and HTTPS serving the same application unintentionally.",[16,13301,13302],{},"Basic hardening in this layout includes:",[63,13304,13305,13308,13311,13317,13320],{},[66,13306,13307],{},"keep Uvicorn off the public edge",[66,13309,13310],{},"deny hidden files",[66,13312,13313,13314],{},"cap request body size with ",[20,13315,13316],{},"client_max_body_size",[66,13318,13319],{},"enable secure cookies in Django",[66,13321,13322],{},"enable HSTS only after HTTPS works correctly end to end",[16,13324,13325,13326,13329],{},"You can also set ",[20,13327,13328],{},"server_tokens off;"," in the main Nginx config if you want to reduce version exposure.",[11,13331,13333],{"id":13332},"step-6-verify-the-deployment","Step 6 — Verify the deployment",[16,13335,13336],{},"Check the service:",[106,13338,13340],{"className":108,"code":13339,"language":110,"meta":111,"style":111},"sudo systemctl status project-uvicorn\n",[20,13341,13342],{"__ignoreMap":111},[115,13343,13344,13346,13348,13350],{"class":117,"line":118},[115,13345,2001],{"class":262},[115,13347,3480],{"class":132},[115,13349,1984],{"class":132},[115,13351,12711],{"class":132},[16,13353,13354],{},"If using a Unix socket, test the upstream directly:",[106,13356,13358],{"className":108,"code":13357,"language":110,"meta":111,"style":111},"curl --unix-socket \u002Frun\u002Fuvicorn\u002Fproject.sock http:\u002F\u002Flocalhost\u002F\n",[20,13359,13360],{"__ignoreMap":111},[115,13361,13362,13364,13367,13370],{"class":117,"line":118},[115,13363,2764],{"class":262},[115,13365,13366],{"class":202}," --unix-socket",[115,13368,13369],{"class":132}," \u002Frun\u002Fuvicorn\u002Fproject.sock",[115,13371,13372],{"class":132}," http:\u002F\u002Flocalhost\u002F\n",[16,13374,13375],{},"Test the public path through Nginx:",[106,13377,13379],{"className":108,"code":13378,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\n",[20,13380,13381],{"__ignoreMap":111},[115,13382,13383,13385,13387],{"class":117,"line":118},[115,13384,2764],{"class":262},[115,13386,2767],{"class":202},[115,13388,2770],{"class":132},[16,13390,13391],{},"Verify static files come from Nginx:",[106,13393,13395],{"className":108,"code":13394,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[20,13396,13397],{"__ignoreMap":111},[115,13398,13399,13401,13403],{"class":117,"line":118},[115,13400,2764],{"class":262},[115,13402,2767],{"class":202},[115,13404,13405],{"class":132}," https:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[16,13407,13408,13409,13412],{},"If your app has a health endpoint such as ",[20,13410,13411],{},"\u002Fhealth\u002F",", test that too:",[106,13414,13416],{"className":108,"code":13415,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,13417,13418],{"__ignoreMap":111},[115,13419,13420,13422,13424],{"class":117,"line":118},[115,13421,2764],{"class":262},[115,13423,2767],{"class":202},[115,13425,13426],{"class":132}," https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[16,13428,13429],{},"Confirm security-sensitive behavior:",[63,13431,13432,13435,13438,13441],{},[66,13433,13434],{},"HTTP redirects to HTTPS",[66,13436,13437],{},"requests to unknown hosts are not accepted by Django",[66,13439,13440],{},"admin login works over HTTPS",[66,13442,13443],{},"CSRF-protected forms submit correctly",[16,13445,13446],{},"Run this after deployment as an extra Django settings check:",[106,13448,13450],{"className":108,"code":13449,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fproject\u002Fapp\nsource \u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Factivate\nset -a\n. \u002Fetc\u002Fproject\u002Fproject.env\nset +a\npython manage.py check --deploy\n",[20,13451,13452,13458,13465,13471,13477,13483],{"__ignoreMap":111},[115,13453,13454,13456],{"class":117,"line":118},[115,13455,5303],{"class":202},[115,13457,12319],{"class":132},[115,13459,13460,13462],{"class":117,"line":136},[115,13461,5311],{"class":202},[115,13463,13464],{"class":132}," \u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Factivate\n",[115,13466,13467,13469],{"class":117,"line":149},[115,13468,203],{"class":202},[115,13470,206],{"class":202},[115,13472,13473,13475],{"class":117,"line":162},[115,13474,211],{"class":202},[115,13476,12411],{"class":132},[115,13478,13479,13481],{"class":117,"line":175},[115,13480,203],{"class":202},[115,13482,221],{"class":132},[115,13484,13485,13487,13489,13491],{"class":117,"line":350},[115,13486,1114],{"class":262},[115,13488,1117],{"class":132},[115,13490,1814],{"class":132},[115,13492,1817],{"class":202},[16,13494,13495,13496,13499,13500,211],{},"This ",[20,13497,13498],{},"set -a"," pattern assumes your env file is shell-compatible. If you use a different env format, use a dedicated loader instead of trying to parse it with ",[20,13501,13502],{},"export $(...)",[11,13504,13506],{"id":13505},"step-7-safe-release-and-rollback-workflow","Step 7 — Safe release and rollback workflow",[16,13508,13509],{},"A simple release flow on one server looks like this:",[106,13511,13513],{"className":108,"code":13512,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fproject\u002Fapp\ngit pull\nsource \u002Fsrv\u002Fproject\u002F.venv\u002Fbin\u002Factivate\npip install -r requirements.txt\nset -a\n. \u002Fetc\u002Fproject\u002Fproject.env\nset +a\npython -c \"from project.asgi import application; print(application)\"\npython manage.py migrate\npython manage.py collectstatic --noinput\nsudo systemctl restart project-uvicorn\nsudo systemctl status project-uvicorn\n",[20,13514,13515,13521,13529,13535,13545,13551,13557,13563,13571,13579,13589,13599],{"__ignoreMap":111},[115,13516,13517,13519],{"class":117,"line":118},[115,13518,5303],{"class":202},[115,13520,12319],{"class":132},[115,13522,13523,13526],{"class":117,"line":136},[115,13524,13525],{"class":262},"git",[115,13527,13528],{"class":132}," pull\n",[115,13530,13531,13533],{"class":117,"line":149},[115,13532,5311],{"class":202},[115,13534,13464],{"class":132},[115,13536,13537,13539,13541,13543],{"class":117,"line":162},[115,13538,8618],{"class":262},[115,13540,6600],{"class":132},[115,13542,12350],{"class":202},[115,13544,12353],{"class":132},[115,13546,13547,13549],{"class":117,"line":175},[115,13548,203],{"class":202},[115,13550,206],{"class":202},[115,13552,13553,13555],{"class":117,"line":350},[115,13554,211],{"class":202},[115,13556,12411],{"class":132},[115,13558,13559,13561],{"class":117,"line":365},[115,13560,203],{"class":202},[115,13562,221],{"class":132},[115,13564,13565,13567,13569],{"class":117,"line":380},[115,13566,1114],{"class":262},[115,13568,1024],{"class":202},[115,13570,11828],{"class":132},[115,13572,13573,13575,13577],{"class":117,"line":487},[115,13574,1114],{"class":262},[115,13576,1117],{"class":132},[115,13578,11324],{"class":132},[115,13580,13581,13583,13585,13587],{"class":117,"line":2095},[115,13582,1114],{"class":262},[115,13584,1117],{"class":132},[115,13586,1838],{"class":132},[115,13588,1841],{"class":202},[115,13590,13591,13593,13595,13597],{"class":117,"line":2104},[115,13592,2001],{"class":262},[115,13594,3480],{"class":132},[115,13596,3483],{"class":132},[115,13598,12711],{"class":132},[115,13600,13601,13603,13605,13607],{"class":117,"line":2113},[115,13602,2001],{"class":262},[115,13604,3480],{"class":132},[115,13606,1984],{"class":132},[115,13608,12711],{"class":132},[16,13610,13611],{},"Reload Nginx only if its config changed:",[106,13613,13614],{"className":108,"code":3897,"language":110,"meta":111,"style":111},[20,13615,13616],{"__ignoreMap":111},[115,13617,13618,13620,13622,13624,13626,13628,13630,13632],{"class":117,"line":118},[115,13619,2001],{"class":262},[115,13621,3906],{"class":132},[115,13623,3909],{"class":202},[115,13625,3912],{"class":125},[115,13627,2001],{"class":262},[115,13629,3480],{"class":132},[115,13631,3919],{"class":132},[115,13633,1996],{"class":132},[16,13635,13636],{},"Verification after restart:",[106,13638,13639],{"className":108,"code":13378,"language":110,"meta":111,"style":111},[20,13640,13641],{"__ignoreMap":111},[115,13642,13643,13645,13647],{"class":117,"line":118},[115,13644,2764],{"class":262},[115,13646,2767],{"class":202},[115,13648,2770],{"class":132},[16,13650,13651,13652,13655],{},"For safer deployments, keep the previous release available instead of editing the live code directory in place. A common pattern is release directories plus a ",[20,13653,13654],{},"current"," symlink, so you can switch back quickly if the new code fails before or after restart.",[16,13657,13658],{},"Rollback guidance:",[63,13660,13661,13664,13670,13673],{},[66,13662,13663],{},"keep the previous code version available",[66,13665,13666,13667,13669],{},"revert to the previous commit or switch the ",[20,13668,13654],{}," symlink back",[66,13671,13672],{},"restart Uvicorn",[66,13674,13675],{},"verify health before considering the rollback complete",[16,13677,13678],{},"Database rollback is separate and riskier than code rollback. If a deployment includes destructive schema changes, plan the migration strategy in advance and consider a database backup or snapshot before applying risky migrations.",[52,13680,4319],{"id":4318},[16,13682,13683,13684,13686,13687,13690],{},"Once you have done this process a few times, the repetitive parts are good automation candidates: virtualenv setup, environment file placement, ",[20,13685,10296],{}," + ",[20,13688,13689],{},"collectstatic",", ASGI import validation, service restart, Nginx config test, and post-deploy health checks. A reusable script or template becomes useful when you are repeating the same Django ASGI pattern across multiple apps or servers.",[11,13692,1321],{"id":1320},[52,13694,13696],{"id":13695},"why-uvicorn-is-used-for-django-asgi","Why Uvicorn is used for Django ASGI",[16,13698,13699],{},"Uvicorn is a production ASGI server. It runs Django’s ASGI application directly and is a good fit when you want ASGI support, including async request handling and long-lived connections.",[52,13701,13703],{"id":13702},"why-nginx-stays-in-front","Why Nginx stays in front",[16,13705,13706],{},"Nginx handles the edge concerns Uvicorn should not manage alone:",[63,13708,13709,13711,13714,13717,13720],{},[66,13710,1785],{},[66,13712,13713],{},"reverse proxying",[66,13715,13716],{},"static file serving",[66,13718,13719],{},"request buffering and timeouts",[66,13721,13722],{},"basic request filtering",[16,13724,13725],{},"This keeps the app server focused on the Django application.",[52,13727,13729],{"id":13728},"when-to-use-a-different-pattern","When to use a different pattern",[16,13731,13732],{},"Use a different deployment pattern if:",[63,13734,13735,13738,13741],{},[66,13736,13737],{},"you want Gunicorn managing process workers with Uvicorn workers",[66,13739,13740],{},"you use Django Channels and need a more explicit WebSocket architecture",[66,13742,13743,13744],{},"you deploy in containers and want the process model handled by an orchestrator instead of ",[20,13745,1277],{},[11,13747,1337],{"id":1336},[52,13749,13751],{"id":13750},"if-the-app-uses-websockets","If the app uses WebSockets",[16,13753,13754],{},"Nginx needs upgrade headers for WebSockets. Add these in the relevant proxied location:",[106,13756,13758],{"className":2154,"code":13757,"language":2156,"meta":111,"style":111},"proxy_http_version 1.1;\nproxy_set_header Upgrade $http_upgrade;\nproxy_set_header Connection \"upgrade\";\n",[20,13759,13760,13769,13776],{"__ignoreMap":111},[115,13761,13762,13765,13767],{"class":117,"line":118},[115,13763,13764],{"class":121},"proxy_http_version ",[115,13766,12879],{"class":202},[115,13768,3811],{"class":125},[115,13770,13771,13773],{"class":117,"line":136},[115,13772,8088],{"class":121},[115,13774,13775],{"class":125},"Upgrade $http_upgrade;\n",[115,13777,13778,13780,13783,13786],{"class":117,"line":149},[115,13779,8088],{"class":121},[115,13781,13782],{"class":125},"Connection ",[115,13784,13785],{"class":132},"\"upgrade\"",[115,13787,3811],{"class":125},[16,13789,13790],{},"Then verify end-to-end behavior with a real WebSocket client, not only normal HTTP requests.",[52,13792,13794],{"id":13793},"if-using-a-unix-socket","If using a Unix socket",[16,13796,13797,13798,13801],{},"Most ",[20,13799,13800],{},"502"," errors come from one of these:",[63,13803,13804,13807,13810],{},[66,13805,13806],{},"Uvicorn never started, so the socket was never created",[66,13808,13809],{},"the socket file exists but Nginx cannot access it",[66,13811,13812,13813,13815],{},"the ",[20,13814,12647],{}," directory was not created with the expected ownership and mode",[16,13817,13818,13819,1153,13822,13825],{},"Using ",[20,13820,13821],{},"RuntimeDirectory=",[20,13823,13824],{},"RuntimeDirectoryMode=",", and a shared group between the service and Nginx makes this more reliable.",[52,13827,13829],{"id":13828},"if-static-files-return-404","If static files return 404",[16,13831,13832],{},"Check these three items first:",[63,13834,13835,13842,13847],{},[66,13836,13837,13839,13840],{},[20,13838,11918],{}," matches the Nginx ",[20,13841,13001],{},[66,13843,13844,13846],{},[20,13845,13689],{}," actually ran successfully",[66,13848,13849],{},"file permissions allow Nginx to read the files",[52,13851,13853],{"id":13852},"if-django-thinks-requests-are-http","If Django thinks requests are HTTP",[16,13855,13856],{},"That usually means one of these is missing or wrong:",[63,13858,13859,13864],{},[66,13860,13861,13862],{},"Nginx does not send ",[20,13863,3203],{},[66,13865,13866,13867],{},"Django does not set ",[20,13868,13869],{},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")",[52,13871,13873],{"id":13872},"if-host-or-csrf-errors-appear-behind-nginx","If host or CSRF errors appear behind Nginx",[16,13875,5438],{},[63,13877,13878,13882,13886,13892],{},[66,13879,13880],{},[20,13881,2719],{},[66,13883,13884],{},[20,13885,2725],{},[66,13887,13888,13889],{},"whether you need ",[20,13890,13891],{},"USE_X_FORWARDED_HOST = True",[66,13893,13894,13895,3146,13897,13899],{},"whether Nginx forwards ",[20,13896,3648],{},[20,13898,7291],{}," as expected",[11,13901,1386],{"id":1385},[16,13903,13904],{},"If you need more background or troubleshooting, these pages fit naturally with this setup:",[63,13906,13907,13914,13920,13926],{},[66,13908,13909,13910,13913],{},"read ",[1395,13911,13912],{"href":3013},"What Is ASGI in Django and When to Use It in Production"," for the ASGI vs WSGI decision",[66,13915,13916,13917,13919],{},"compare ",[1395,13918,2986],{"href":2985}," if you want the WSGI pattern instead",[66,13921,13922,13923,13925],{},"follow ",[1395,13924,3000],{"href":2999}," before going live",[66,13927,13928,13929,13931],{},"check ",[1395,13930,2993],{"href":2992}," if you want a container-based process model",[11,13933,1420],{"id":1419},[52,13935,13937],{"id":13936},"can-i-deploy-django-asgi-with-uvicorn-and-nginx-without-docker","Can I deploy Django ASGI with Uvicorn and Nginx without Docker?",[16,13939,13940,13941,13943],{},"Yes. This guide is a non-Docker deployment. ",[20,13942,1277],{}," plus Nginx is a normal production pattern on Linux servers.",[52,13945,13947],{"id":13946},"should-i-use-a-unix-socket-or-a-local-tcp-port-for-uvicorn","Should I use a Unix socket or a local TCP port for Uvicorn?",[16,13949,13950],{},"For a single server with local Nginx, a Unix socket is usually a good default. A local TCP port is simpler to inspect with common tools and may be easier in some environments. Either is valid if permissions and proxy settings are correct.",[52,13952,13954],{"id":13953},"do-i-need-gunicorn-if-i-already-use-uvicorn-for-django-asgi","Do I need Gunicorn if I already use Uvicorn for Django ASGI?",[16,13956,13957],{},"No. Uvicorn can run Django ASGI directly. Gunicorn is optional if you specifically want Gunicorn’s worker supervision model.",[52,13959,13961],{"id":13960},"how-do-i-handle-websockets-with-django-uvicorn-and-nginx","How do I handle WebSockets with Django, Uvicorn, and Nginx?",[16,13963,13964],{},"Keep Django on ASGI, make sure your app routing supports WebSockets if needed, and add Nginx upgrade headers so the connection can be upgraded properly. Then test with a real WebSocket client, not only with normal HTTP requests.",[52,13966,13968],{"id":13967},"what-is-the-safest-way-to-restart-the-app-during-releases","What is the safest way to restart the app during releases?",[16,13970,13971],{},"Apply code and dependencies, validate the ASGI import before restart, run migrations carefully, collect static files, then restart only the Uvicorn service. Reload Nginx only if its configuration changed. For safer rollbacks, keep the previous release available so you can switch back quickly.",[1485,13973,13974],{},"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 .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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":111,"searchDepth":149,"depth":149,"links":13976},[13977,13978,13979,13980,13985,13988,13992,13997,13998,13999,14002,14007,14014,14015],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":11744,"depth":136,"text":11745,"children":13981},[13982,13983,13984],{"id":11748,"depth":149,"text":11749},{"id":11834,"depth":149,"text":11835},{"id":12175,"depth":149,"text":12176},{"id":12225,"depth":136,"text":12226,"children":13986},[13987],{"id":12381,"depth":149,"text":12382},{"id":12494,"depth":136,"text":12495,"children":13989},[13990,13991],{"id":12498,"depth":149,"text":12499},{"id":12505,"depth":149,"text":12506},{"id":12781,"depth":136,"text":12782,"children":13993},[13994,13995,13996],{"id":12785,"depth":149,"text":12786},{"id":12994,"depth":149,"text":12995},{"id":13092,"depth":149,"text":13093},{"id":13198,"depth":136,"text":13199},{"id":13332,"depth":136,"text":13333},{"id":13505,"depth":136,"text":13506,"children":14000},[14001],{"id":4318,"depth":149,"text":4319},{"id":1320,"depth":136,"text":1321,"children":14003},[14004,14005,14006],{"id":13695,"depth":149,"text":13696},{"id":13702,"depth":149,"text":13703},{"id":13728,"depth":149,"text":13729},{"id":1336,"depth":136,"text":1337,"children":14008},[14009,14010,14011,14012,14013],{"id":13750,"depth":149,"text":13751},{"id":13793,"depth":149,"text":13794},{"id":13828,"depth":149,"text":13829},{"id":13852,"depth":149,"text":13853},{"id":13872,"depth":149,"text":13873},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":14016},[14017,14018,14019,14020,14021],{"id":13936,"depth":149,"text":13937},{"id":13946,"depth":149,"text":13947},{"id":13953,"depth":149,"text":13954},{"id":13960,"depth":149,"text":13961},{"id":13967,"depth":149,"text":13968},"If you want to deploy Django ASGI with Uvicorn and Nginx, python manage.py runserver is not a production option.",{},"\u002Fdeploy-django-uvicorn-nginx","5",[2985,2992,14027],"\u002Fdeploy\u002Fdeploy-django-on-digitalocean",{"title":8039,"description":14022},[1557,12359,2156],"deploy-django-uvicorn-nginx",[1557,12359,2156],"NC6swflu1920o7GPP6B58h53mRgTp2IHU-8iwyRTBPQ",{"id":14034,"title":14035,"body":14036,"category":3088,"description":16238,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":16239,"navigation":309,"path":16240,"priority":16241,"related":16242,"role":1553,"section":3098,"seo":16244,"stack":16245,"stem":16247,"tags":16248,"type":1561,"__hash__":16249},"articles\u002Fdeploy-django-on-aws-ec2.md","Deploy Django on AWS EC2 Step by Step",{"type":8,"value":14037,"toc":16208},[14038,14040,14046,14052,14054,14057,14083,14086,14129,14131,14135,14145,14148,14164,14167,14170,14175,14194,14198,14201,14289,14292,14376,14385,14400,14403,14419,14423,14447,14450,14454,14457,14500,14503,14524,14527,14544,14547,14551,14575,14579,14582,14585,14602,14731,14735,14752,14755,14759,14762,14800,14803,14822,14825,14863,14866,14870,14884,14888,14935,14938,14957,14967,14970,14974,14977,14982,14996,14998,15036,15039,15065,15068,15071,15162,15168,15171,15174,15220,15223,15250,15254,15257,15261,15264,15309,15312,15326,15329,15333,15338,15454,15457,15502,15505,15526,15530,15546,15550,15555,15691,15694,15743,15747,15768,15771,15775,15778,15813,15816,15819,15846,15852,15855,15859,15871,15875,15878,15895,15898,15950,15952,15972,15976,15979,16005,16008,16011,16013,16016,16018,16021,16032,16035,16052,16054,16057,16104,16107,16110,16112,16120,16127,16134,16141,16143,16147,16153,16157,16160,16164,16167,16171,16182,16186,16188,16202,16205],[11,14039,14],{"id":13},[16,14041,14042,14043,14045],{},"To deploy Django on AWS EC2 reliably, you need more than ",[20,14044,11657],{},". A production setup has to handle process supervision, reverse proxying, static files, database migrations, TLS, secrets, restarts, and rollback.",[16,14047,14048,14049,14051],{},"The common failure modes are predictable: Gunicorn is running but Nginx cannot reach it, static files return 404, ",[20,14050,2719],{}," blocks requests, migrations break the release, or HTTPS is enabled but Django still thinks requests are insecure. A good EC2 deployment avoids those issues with a repeatable structure and clear verification at each step.",[11,14053,30],{"id":29},[16,14055,14056],{},"A solid baseline for a Django AWS EC2 deployment is:",[63,14058,14059,14062,14065,14068,14071,14074,14077,14080],{},[66,14060,14061],{},"Ubuntu LTS on EC2",[66,14063,14064],{},"Python virtualenv",[66,14066,14067],{},"Gunicorn as the app server",[66,14069,14070],{},"Nginx as the reverse proxy",[66,14072,14073],{},"systemd for service management",[66,14075,14076],{},"PostgreSQL for production data",[66,14078,14079],{},"optional Redis for caching or background jobs",[66,14081,14082],{},"Let's Encrypt for TLS",[16,14084,14085],{},"High-level order:",[1173,14087,14088,14091,14094,14097,14100,14103,14106,14109,14114,14117,14120,14123,14126],{},[66,14089,14090],{},"Provision Ubuntu EC2",[66,14092,14093],{},"Restrict network access and secure SSH",[66,14095,14096],{},"Install Python, Nginx, and system packages",[66,14098,14099],{},"Set up PostgreSQL if you are running it on the instance",[66,14101,14102],{},"Upload the app into a release-based directory layout",[66,14104,14105],{},"Create a virtualenv and install dependencies",[66,14107,14108],{},"Configure Django production settings and secrets",[66,14110,14111,14112],{},"Run checks, migrations, and ",[20,14113,13689],{},[66,14115,14116],{},"Configure Gunicorn with systemd",[66,14118,14119],{},"Configure Nginx",[66,14121,14122],{},"Enable HTTPS",[66,14124,14125],{},"Verify the deployment",[66,14127,14128],{},"Keep rollback paths for code and backups for data",[11,14130,43],{"id":42},[11,14132,14134],{"id":14133},"_1-provision-the-aws-ec2-instance","1) Provision the AWS EC2 instance",[16,14136,14137,14138,4493,14141,14144],{},"Use Ubuntu 22.04 LTS or 24.04 LTS. For a small app, ",[20,14139,14140],{},"t3.small",[20,14142,14143],{},"t3.medium"," is a reasonable starting point. Scale based on memory pressure, worker count, and database load.",[16,14146,14147],{},"In the EC2 security group, allow only:",[63,14149,14150,14155,14160],{},[66,14151,14152,14154],{},[20,14153,10241],{}," from your admin IP range",[66,14156,14157,14159],{},[20,14158,3808],{}," from the internet",[66,14161,14162,14159],{},[20,14163,2174],{},[16,14165,14166],{},"If this server will keep local uploads or a local database, size the EBS volume accordingly and enable snapshots.",[16,14168,14169],{},"Point your domain or subdomain to the instance public IP. An Elastic IP is useful if you want a stable address across instance stop\u002Fstart cycles.",[16,14171,14172],{},[1226,14173,14174],{},"Verify",[106,14176,14178],{"className":108,"code":14177,"language":110,"meta":111,"style":111},"ssh -i your-key.pem ubuntu@your-server-ip\n",[20,14179,14180],{"__ignoreMap":111},[115,14181,14182,14185,14188,14191],{"class":117,"line":118},[115,14183,14184],{"class":262},"ssh",[115,14186,14187],{"class":202}," -i",[115,14189,14190],{"class":132}," your-key.pem",[115,14192,14193],{"class":132}," ubuntu@your-server-ip\n",[11,14195,14197],{"id":14196},"_2-secure-the-server-before-deploying-django","2) Secure the server before deploying Django",[16,14199,14200],{},"Create a non-root sudo user and use key-based SSH.",[106,14202,14204],{"className":108,"code":14203,"language":110,"meta":111,"style":111},"sudo adduser deploy\nsudo usermod -aG sudo deploy\nsudo mkdir -p \u002Fhome\u002Fdeploy\u002F.ssh\nsudo cp \u002Fhome\u002Fubuntu\u002F.ssh\u002Fauthorized_keys \u002Fhome\u002Fdeploy\u002F.ssh\u002F\nsudo chown -R deploy:deploy \u002Fhome\u002Fdeploy\u002F.ssh\nsudo chmod 700 \u002Fhome\u002Fdeploy\u002F.ssh\nsudo chmod 600 \u002Fhome\u002Fdeploy\u002F.ssh\u002Fauthorized_keys\n",[20,14205,14206,14216,14231,14242,14254,14267,14278],{"__ignoreMap":111},[115,14207,14208,14210,14213],{"class":117,"line":118},[115,14209,2001],{"class":262},[115,14211,14212],{"class":132}," adduser",[115,14214,14215],{"class":132}," deploy\n",[115,14217,14218,14220,14223,14226,14229],{"class":117,"line":136},[115,14219,2001],{"class":262},[115,14221,14222],{"class":132}," usermod",[115,14224,14225],{"class":202}," -aG",[115,14227,14228],{"class":132}," sudo",[115,14230,14215],{"class":132},[115,14232,14233,14235,14237,14239],{"class":117,"line":149},[115,14234,2001],{"class":262},[115,14236,6721],{"class":132},[115,14238,1001],{"class":202},[115,14240,14241],{"class":132}," \u002Fhome\u002Fdeploy\u002F.ssh\n",[115,14243,14244,14246,14248,14251],{"class":117,"line":162},[115,14245,2001],{"class":262},[115,14247,6516],{"class":132},[115,14249,14250],{"class":132}," \u002Fhome\u002Fubuntu\u002F.ssh\u002Fauthorized_keys",[115,14252,14253],{"class":132}," \u002Fhome\u002Fdeploy\u002F.ssh\u002F\n",[115,14255,14256,14258,14260,14262,14265],{"class":117,"line":175},[115,14257,2001],{"class":262},[115,14259,6733],{"class":132},[115,14261,6736],{"class":202},[115,14263,14264],{"class":132}," deploy:deploy",[115,14266,14241],{"class":132},[115,14268,14269,14271,14273,14276],{"class":117,"line":350},[115,14270,2001],{"class":262},[115,14272,12480],{"class":132},[115,14274,14275],{"class":202}," 700",[115,14277,14241],{"class":132},[115,14279,14280,14282,14284,14286],{"class":117,"line":365},[115,14281,2001],{"class":262},[115,14283,12480],{"class":132},[115,14285,266],{"class":202},[115,14287,14288],{"class":132}," \u002Fhome\u002Fdeploy\u002F.ssh\u002Fauthorized_keys\n",[16,14290,14291],{},"Now install basic protections and update packages:",[106,14293,14295],{"className":108,"code":14294,"language":110,"meta":111,"style":111},"sudo apt update && sudo apt upgrade -y\nsudo apt install -y ufw fail2ban\nsudo ufw allow OpenSSH\nsudo ufw allow 80\nsudo ufw allow 443\nsudo ufw enable\n",[20,14296,14297,14318,14333,14345,14356,14367],{"__ignoreMap":111},[115,14298,14299,14301,14303,14306,14308,14310,14312,14315],{"class":117,"line":118},[115,14300,2001],{"class":262},[115,14302,6588],{"class":132},[115,14304,14305],{"class":132}," update",[115,14307,3912],{"class":125},[115,14309,2001],{"class":262},[115,14311,6588],{"class":132},[115,14313,14314],{"class":132}," upgrade",[115,14316,14317],{"class":202}," -y\n",[115,14319,14320,14322,14324,14326,14328,14330],{"class":117,"line":136},[115,14321,2001],{"class":262},[115,14323,6588],{"class":132},[115,14325,6600],{"class":132},[115,14327,8432],{"class":202},[115,14329,2014],{"class":132},[115,14331,14332],{"class":132}," fail2ban\n",[115,14334,14335,14337,14339,14342],{"class":117,"line":149},[115,14336,2001],{"class":262},[115,14338,2014],{"class":132},[115,14340,14341],{"class":132}," allow",[115,14343,14344],{"class":132}," OpenSSH\n",[115,14346,14347,14349,14351,14353],{"class":117,"line":162},[115,14348,2001],{"class":262},[115,14350,2014],{"class":132},[115,14352,14341],{"class":132},[115,14354,14355],{"class":202}," 80\n",[115,14357,14358,14360,14362,14364],{"class":117,"line":175},[115,14359,2001],{"class":262},[115,14361,2014],{"class":132},[115,14363,14341],{"class":132},[115,14365,14366],{"class":202}," 443\n",[115,14368,14369,14371,14373],{"class":117,"line":350},[115,14370,2001],{"class":262},[115,14372,2014],{"class":132},[115,14374,14375],{"class":132}," enable\n",[16,14377,14378,14379,14381,14382,241],{},"If you control all SSH clients and have verified key login works for ",[20,14380,3098],{},", disable password auth and root SSH login in ",[20,14383,14384],{},"\u002Fetc\u002Fssh\u002Fsshd_config",[106,14386,14388],{"className":8444,"code":14387,"language":8446,"meta":111,"style":111},"PasswordAuthentication no\nPermitRootLogin no\n",[20,14389,14390,14395],{"__ignoreMap":111},[115,14391,14392],{"class":117,"line":118},[115,14393,14394],{},"PasswordAuthentication no\n",[115,14396,14397],{"class":117,"line":136},[115,14398,14399],{},"PermitRootLogin no\n",[16,14401,14402],{},"Then reload SSH:",[106,14404,14406],{"className":108,"code":14405,"language":110,"meta":111,"style":111},"sudo systemctl reload ssh\n",[20,14407,14408],{"__ignoreMap":111},[115,14409,14410,14412,14414,14416],{"class":117,"line":118},[115,14411,2001],{"class":262},[115,14413,3480],{"class":132},[115,14415,3919],{"class":132},[115,14417,14418],{"class":132}," ssh\n",[16,14420,14421],{},[1226,14422,14174],{},[106,14424,14426],{"className":108,"code":14425,"language":110,"meta":111,"style":111},"ssh -i your-key.pem deploy@your-server-ip\nsudo ufw status\n",[20,14427,14428,14439],{"__ignoreMap":111},[115,14429,14430,14432,14434,14436],{"class":117,"line":118},[115,14431,14184],{"class":262},[115,14433,14187],{"class":202},[115,14435,14190],{"class":132},[115,14437,14438],{"class":132}," deploy@your-server-ip\n",[115,14440,14441,14443,14445],{"class":117,"line":136},[115,14442,2001],{"class":262},[115,14444,2014],{"class":132},[115,14446,2017],{"class":132},[16,14448,14449],{},"Rollback note: do not close your current SSH session before confirming the new user can log in.",[11,14451,14453],{"id":14452},"_3-install-runtime-and-system-packages","3) Install runtime and system packages",[16,14455,14456],{},"Install Python, venv tools, Nginx, Git, and build dependencies commonly needed by Django packages.",[106,14458,14460],{"className":108,"code":14459,"language":110,"meta":111,"style":111},"sudo apt install -y python3 python3-venv python3-pip \\\n    nginx git build-essential libpq-dev pkg-config\n",[20,14461,14462,14483],{"__ignoreMap":111},[115,14463,14464,14466,14468,14470,14472,14475,14478,14481],{"class":117,"line":118},[115,14465,2001],{"class":262},[115,14467,6588],{"class":132},[115,14469,6600],{"class":132},[115,14471,8432],{"class":202},[115,14473,14474],{"class":132}," python3",[115,14476,14477],{"class":132}," python3-venv",[115,14479,14480],{"class":132}," python3-pip",[115,14482,317],{"class":202},[115,14484,14485,14488,14491,14494,14497],{"class":117,"line":136},[115,14486,14487],{"class":132},"    nginx",[115,14489,14490],{"class":132}," git",[115,14492,14493],{"class":132}," build-essential",[115,14495,14496],{"class":132}," libpq-dev",[115,14498,14499],{"class":132}," pkg-config\n",[16,14501,14502],{},"If your app uses PostgreSQL locally on the server:",[106,14504,14506],{"className":108,"code":14505,"language":110,"meta":111,"style":111},"sudo apt install -y postgresql postgresql-contrib\n",[20,14507,14508],{"__ignoreMap":111},[115,14509,14510,14512,14514,14516,14518,14521],{"class":117,"line":118},[115,14511,2001],{"class":262},[115,14513,6588],{"class":132},[115,14515,6600],{"class":132},[115,14517,8432],{"class":202},[115,14519,14520],{"class":132}," postgresql",[115,14522,14523],{"class":132}," postgresql-contrib\n",[16,14525,14526],{},"If your app uses Redis:",[106,14528,14530],{"className":108,"code":14529,"language":110,"meta":111,"style":111},"sudo apt install -y redis-server\n",[20,14531,14532],{"__ignoreMap":111},[115,14533,14534,14536,14538,14540,14542],{"class":117,"line":118},[115,14535,2001],{"class":262},[115,14537,6588],{"class":132},[115,14539,6600],{"class":132},[115,14541,8432],{"class":202},[115,14543,8435],{"class":132},[16,14545,14546],{},"For many production setups, a managed database is better than a local PostgreSQL install. But for a single EC2 deployment, local PostgreSQL is still common and simpler to start with.",[16,14548,14549],{},[1226,14550,14174],{},[106,14552,14554],{"className":108,"code":14553,"language":110,"meta":111,"style":111},"python3 --version\nnginx -v\ngit --version\n",[20,14555,14556,14562,14569],{"__ignoreMap":111},[115,14557,14558,14560],{"class":117,"line":118},[115,14559,12281],{"class":262},[115,14561,6614],{"class":202},[115,14563,14564,14566],{"class":117,"line":136},[115,14565,2156],{"class":262},[115,14567,14568],{"class":202}," -v\n",[115,14570,14571,14573],{"class":117,"line":149},[115,14572,13525],{"class":262},[115,14574,6614],{"class":202},[11,14576,14578],{"id":14577},"_4-set-up-postgresql-if-it-will-run-on-this-ec2-instance","4) Set up PostgreSQL if it will run on this EC2 instance",[16,14580,14581],{},"If you are using RDS or another managed database, skip this step and use that connection string in your environment file.",[16,14583,14584],{},"For local PostgreSQL, create the database and user before running Django migrations:",[106,14586,14588],{"className":108,"code":14587,"language":110,"meta":111,"style":111},"sudo -u postgres psql\n",[20,14589,14590],{"__ignoreMap":111},[115,14591,14592,14594,14596,14599],{"class":117,"line":118},[115,14593,2001],{"class":262},[115,14595,2788],{"class":202},[115,14597,14598],{"class":132}," postgres",[115,14600,14601],{"class":132}," psql\n",[106,14603,14605],{"className":11064,"code":14604,"language":11066,"meta":111,"style":111},"CREATE DATABASE myapp;\nCREATE USER myappuser WITH PASSWORD 'strongpassword';\nALTER ROLE myappuser SET client_encoding TO 'utf8';\nALTER ROLE myappuser SET default_transaction_isolation TO 'read committed';\nALTER ROLE myappuser SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE myapp TO myappuser;\n\\q\n",[20,14606,14607,14619,14640,14665,14685,14705,14726],{"__ignoreMap":111},[115,14608,14609,14612,14615,14617],{"class":117,"line":118},[115,14610,14611],{"class":121},"CREATE",[115,14613,14614],{"class":121}," DATABASE",[115,14616,5325],{"class":262},[115,14618,3811],{"class":125},[115,14620,14621,14623,14626,14629,14632,14635,14638],{"class":117,"line":136},[115,14622,14611],{"class":121},[115,14624,14625],{"class":121}," USER",[115,14627,14628],{"class":262}," myappuser",[115,14630,14631],{"class":121}," WITH",[115,14633,14634],{"class":121}," PASSWORD",[115,14636,14637],{"class":132}," 'strongpassword'",[115,14639,3811],{"class":125},[115,14641,14642,14645,14648,14651,14654,14657,14660,14663],{"class":117,"line":149},[115,14643,14644],{"class":121},"ALTER",[115,14646,14647],{"class":121}," ROLE",[115,14649,14650],{"class":125}," myappuser ",[115,14652,14653],{"class":121},"SET",[115,14655,14656],{"class":125}," client_encoding ",[115,14658,14659],{"class":121},"TO",[115,14661,14662],{"class":132}," 'utf8'",[115,14664,3811],{"class":125},[115,14666,14667,14669,14671,14673,14675,14678,14680,14683],{"class":117,"line":162},[115,14668,14644],{"class":121},[115,14670,14647],{"class":121},[115,14672,14650],{"class":125},[115,14674,14653],{"class":121},[115,14676,14677],{"class":125}," default_transaction_isolation ",[115,14679,14659],{"class":121},[115,14681,14682],{"class":132}," 'read committed'",[115,14684,3811],{"class":125},[115,14686,14687,14689,14691,14693,14695,14698,14700,14703],{"class":117,"line":175},[115,14688,14644],{"class":121},[115,14690,14647],{"class":121},[115,14692,14650],{"class":125},[115,14694,14653],{"class":121},[115,14696,14697],{"class":125}," timezone ",[115,14699,14659],{"class":121},[115,14701,14702],{"class":132}," 'UTC'",[115,14704,3811],{"class":125},[115,14706,14707,14710,14713,14716,14718,14721,14723],{"class":117,"line":350},[115,14708,14709],{"class":121},"GRANT",[115,14711,14712],{"class":125}," ALL PRIVILEGES ",[115,14714,14715],{"class":121},"ON",[115,14717,14614],{"class":121},[115,14719,14720],{"class":125}," myapp ",[115,14722,14659],{"class":121},[115,14724,14725],{"class":125}," myappuser;\n",[115,14727,14728],{"class":117,"line":365},[115,14729,14730],{"class":125},"\\q\n",[16,14732,14733],{},[1226,14734,14174],{},[106,14736,14738],{"className":108,"code":14737,"language":110,"meta":111,"style":111},"psql \"postgresql:\u002F\u002Fmyappuser:strongpassword@127.0.0.1:5432\u002Fmyapp\" -c '\\conninfo'\n",[20,14739,14740],{"__ignoreMap":111},[115,14741,14742,14744,14747,14749],{"class":117,"line":118},[115,14743,835],{"class":262},[115,14745,14746],{"class":132}," \"postgresql:\u002F\u002Fmyappuser:strongpassword@127.0.0.1:5432\u002Fmyapp\"",[115,14748,1024],{"class":202},[115,14750,14751],{"class":132}," '\\conninfo'\n",[16,14753,14754],{},"If local PostgreSQL is only for this app, make sure you also include it in your backup plan.",[11,14756,14758],{"id":14757},"_5-upload-the-django-app-with-a-rollback-friendly-layout","5) Upload the Django app with a rollback-friendly layout",[16,14760,14761],{},"Create a release-based directory structure:",[106,14763,14765],{"className":108,"code":14764,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fsrv\u002Fmyapp\u002Freleases \u002Fsrv\u002Fmyapp\u002Fshared \u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\nsudo chown -R deploy:deploy \u002Fsrv\u002Fmyapp\n",[20,14766,14767,14787],{"__ignoreMap":111},[115,14768,14769,14771,14773,14775,14778,14781,14784],{"class":117,"line":118},[115,14770,2001],{"class":262},[115,14772,6721],{"class":132},[115,14774,1001],{"class":202},[115,14776,14777],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases",[115,14779,14780],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fshared",[115,14782,14783],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic",[115,14785,14786],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\n",[115,14788,14789,14791,14793,14795,14797],{"class":117,"line":136},[115,14790,2001],{"class":262},[115,14792,6733],{"class":132},[115,14794,6736],{"class":202},[115,14796,14264],{"class":132},[115,14798,14799],{"class":132}," \u002Fsrv\u002Fmyapp\n",[16,14801,14802],{},"Example layout:",[63,14804,14805,14810,14816],{},[66,14806,14807],{},[20,14808,14809],{},"\u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000",[66,14811,14812,14815],{},[20,14813,14814],{},"\u002Fsrv\u002Fmyapp\u002Fcurrent"," -> symlink to active release",[66,14817,14818,14821],{},[20,14819,14820],{},"\u002Fsrv\u002Fmyapp\u002Fshared"," for persistent data",[16,14823,14824],{},"Deploy from Git or a release artifact. Example with Git:",[106,14826,14828],{"className":108,"code":14827,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Freleases\ngit clone git@github.com:your-org\u002Fyour-repo.git 20260424-120000\nln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000 \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[20,14829,14830,14837,14850],{"__ignoreMap":111},[115,14831,14832,14834],{"class":117,"line":118},[115,14833,5303],{"class":202},[115,14835,14836],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\n",[115,14838,14839,14841,14844,14847],{"class":117,"line":136},[115,14840,13525],{"class":262},[115,14842,14843],{"class":132}," clone",[115,14845,14846],{"class":132}," git@github.com:your-org\u002Fyour-repo.git",[115,14848,14849],{"class":132}," 20260424-120000\n",[115,14851,14852,14855,14858,14861],{"class":117,"line":149},[115,14853,14854],{"class":262},"ln",[115,14856,14857],{"class":202}," -sfn",[115,14859,14860],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000",[115,14862,5306],{"class":132},[16,14864,14865],{},"If the repo is private, use a deploy key or another reproducible method. Avoid editing code directly on the server.",[16,14867,14868],{},[1226,14869,14174],{},[106,14871,14873],{"className":108,"code":14872,"language":110,"meta":111,"style":111},"ls -l \u002Fsrv\u002Fmyapp\n",[20,14874,14875],{"__ignoreMap":111},[115,14876,14877,14879,14882],{"class":117,"line":118},[115,14878,532],{"class":262},[115,14880,14881],{"class":202}," -l",[115,14883,14799],{"class":132},[11,14885,14887],{"id":14886},"_6-create-the-virtual-environment-and-install-dependencies","6) Create the virtual environment and install dependencies",[106,14889,14891],{"className":108,"code":14890,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\npython3 -m venv .venv\nsource .venv\u002Fbin\u002Factivate\npip install --upgrade pip\npip install -r requirements.txt\n",[20,14892,14893,14899,14909,14915,14925],{"__ignoreMap":111},[115,14894,14895,14897],{"class":117,"line":118},[115,14896,5303],{"class":202},[115,14898,5306],{"class":132},[115,14900,14901,14903,14905,14907],{"class":117,"line":136},[115,14902,12281],{"class":262},[115,14904,12284],{"class":202},[115,14906,12287],{"class":132},[115,14908,12290],{"class":132},[115,14910,14911,14913],{"class":117,"line":149},[115,14912,5311],{"class":202},[115,14914,12297],{"class":132},[115,14916,14917,14919,14921,14923],{"class":117,"line":162},[115,14918,8618],{"class":262},[115,14920,6600],{"class":132},[115,14922,12338],{"class":202},[115,14924,12341],{"class":132},[115,14926,14927,14929,14931,14933],{"class":117,"line":175},[115,14928,8618],{"class":262},[115,14930,6600],{"class":132},[115,14932,12350],{"class":202},[115,14934,12353],{"class":132},[16,14936,14937],{},"Confirm Gunicorn is installed:",[106,14939,14941],{"className":108,"code":14940,"language":110,"meta":111,"style":111},"which gunicorn\ngunicorn --version\n",[20,14942,14943,14950],{"__ignoreMap":111},[115,14944,14945,14948],{"class":117,"line":118},[115,14946,14947],{"class":202},"which",[115,14949,1987],{"class":132},[115,14951,14952,14955],{"class":117,"line":136},[115,14953,14954],{"class":262},"gunicorn",[115,14956,6614],{"class":202},[16,14958,14959,14960,14963,14964,14966],{},"Because this guide keeps ",[20,14961,14962],{},".venv"," inside each release, every new release needs its own dependency install before you switch the ",[20,14965,14814],{}," symlink. If you roll back to an older release, that release must still have a valid virtualenv and dependencies.",[16,14968,14969],{},"If your project is ASGI-first, Uvicorn may be more appropriate, but Gunicorn is a strong default for standard Django WSGI deployments.",[11,14971,14973],{"id":14972},"_7-configure-django-production-settings","7) Configure Django production settings",[16,14975,14976],{},"Store secrets outside the repo. One simple pattern is an env file referenced by systemd.",[16,14978,8628,14979,241],{},[20,14980,14981],{},"\u002Fetc\u002Fmyapp.env",[106,14983,14985],{"className":108,"code":14984,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fmyapp.env\n",[20,14986,14987],{"__ignoreMap":111},[115,14988,14989,14991,14993],{"class":117,"line":118},[115,14990,2001],{"class":262},[115,14992,12408],{"class":132},[115,14994,14995],{"class":132}," \u002Fetc\u002Fmyapp.env\n",[16,14997,12414],{},[106,14999,15001],{"className":2329,"code":15000,"language":2331,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=config.settings.production\nDJANGO_SECRET_KEY=replace-me\nDJANGO_DEBUG=False\nDJANGO_ALLOWED_HOSTS=example.com,www.example.com\nDJANGO_CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\nDATABASE_URL=postgresql:\u002F\u002Fmyappuser:strongpassword@127.0.0.1:5432\u002Fmyapp\nREDIS_URL=redis:\u002F\u002F127.0.0.1:6379\u002F0\n",[20,15002,15003,15007,15011,15016,15021,15026,15031],{"__ignoreMap":111},[115,15004,15005],{"class":117,"line":118},[115,15006,2338],{},[115,15008,15009],{"class":117,"line":136},[115,15010,12429],{},[115,15012,15013],{"class":117,"line":149},[115,15014,15015],{},"DJANGO_DEBUG=False\n",[115,15017,15018],{"class":117,"line":162},[115,15019,15020],{},"DJANGO_ALLOWED_HOSTS=example.com,www.example.com\n",[115,15022,15023],{"class":117,"line":175},[115,15024,15025],{},"DJANGO_CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n",[115,15027,15028],{"class":117,"line":350},[115,15029,15030],{},"DATABASE_URL=postgresql:\u002F\u002Fmyappuser:strongpassword@127.0.0.1:5432\u002Fmyapp\n",[115,15032,15033],{"class":117,"line":365},[115,15034,15035],{},"REDIS_URL=redis:\u002F\u002F127.0.0.1:6379\u002F0\n",[16,15037,15038],{},"Secure it:",[106,15040,15042],{"className":108,"code":15041,"language":110,"meta":111,"style":111},"sudo chown root:deploy \u002Fetc\u002Fmyapp.env\nsudo chmod 640 \u002Fetc\u002Fmyapp.env\n",[20,15043,15044,15055],{"__ignoreMap":111},[115,15045,15046,15048,15050,15053],{"class":117,"line":118},[115,15047,2001],{"class":262},[115,15049,6733],{"class":132},[115,15051,15052],{"class":132}," root:deploy",[115,15054,14995],{"class":132},[115,15056,15057,15059,15061,15063],{"class":117,"line":136},[115,15058,2001],{"class":262},[115,15060,12480],{"class":132},[115,15062,12483],{"class":202},[115,15064,14995],{"class":132},[16,15066,15067],{},"Make sure only the Gunicorn service user can read this file; do not make it world-readable.",[16,15069,15070],{},"In Django production settings, make sure you set:",[106,15072,15074],{"className":2369,"code":15073,"language":1114,"meta":111,"style":111},"DEBUG = False\nALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\", \"https:\u002F\u002Fwww.example.com\"]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_SSL_REDIRECT = True\n",[20,15075,15076,15084,15100,15118,15122,15138,15146,15154],{"__ignoreMap":111},[115,15077,15078,15080,15082],{"class":117,"line":118},[115,15079,7350],{"class":202},[115,15081,2380],{"class":121},[115,15083,7355],{"class":202},[115,15085,15086,15088,15090,15092,15094,15096,15098],{"class":117,"line":136},[115,15087,2719],{"class":202},[115,15089,2380],{"class":121},[115,15091,7493],{"class":125},[115,15093,7496],{"class":132},[115,15095,1153],{"class":125},[115,15097,7501],{"class":132},[115,15099,2552],{"class":125},[115,15101,15102,15104,15106,15108,15111,15113,15116],{"class":117,"line":149},[115,15103,2725],{"class":202},[115,15105,2380],{"class":121},[115,15107,7493],{"class":125},[115,15109,15110],{"class":132},"\"https:\u002F\u002Fexample.com\"",[115,15112,1153],{"class":125},[115,15114,15115],{"class":132},"\"https:\u002F\u002Fwww.example.com\"",[115,15117,2552],{"class":125},[115,15119,15120],{"class":117,"line":162},[115,15121,310],{"emptyLinePlaceholder":309},[115,15123,15124,15126,15128,15130,15132,15134,15136],{"class":117,"line":175},[115,15125,2377],{"class":202},[115,15127,2380],{"class":121},[115,15129,2383],{"class":125},[115,15131,2386],{"class":132},[115,15133,1153],{"class":125},[115,15135,2391],{"class":132},[115,15137,2394],{"class":125},[115,15139,15140,15142,15144],{"class":117,"line":350},[115,15141,2417],{"class":202},[115,15143,2380],{"class":121},[115,15145,2412],{"class":202},[115,15147,15148,15150,15152],{"class":117,"line":365},[115,15149,2426],{"class":202},[115,15151,2380],{"class":121},[115,15153,2412],{"class":202},[115,15155,15156,15158,15160],{"class":117,"line":380},[115,15157,2407],{"class":202},[115,15159,2380],{"class":121},[115,15161,2412],{"class":202},[16,15163,15164,15165,15167],{},"Enable ",[20,15166,13296],{}," only after HTTPS is working correctly, or you can create redirect loops or make the site inaccessible during initial setup.",[16,15169,15170],{},"If you load hosts or origins from environment variables, parse them into Python lists rather than passing a raw comma-separated string directly into Django settings.",[16,15172,15173],{},"For static and media:",[106,15175,15177],{"className":2369,"code":15176,"language":1114,"meta":111,"style":111},"STATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\"\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\"\n",[20,15178,15179,15187,15196,15200,15210],{"__ignoreMap":111},[115,15180,15181,15183,15185],{"class":117,"line":118},[115,15182,11908],{"class":202},[115,15184,2380],{"class":121},[115,15186,11913],{"class":132},[115,15188,15189,15191,15193],{"class":117,"line":136},[115,15190,11918],{"class":202},[115,15192,2380],{"class":121},[115,15194,15195],{"class":132}," \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\"\n",[115,15197,15198],{"class":117,"line":149},[115,15199,310],{"emptyLinePlaceholder":309},[115,15201,15202,15205,15207],{"class":117,"line":162},[115,15203,15204],{"class":202},"MEDIA_URL",[115,15206,2380],{"class":121},[115,15208,15209],{"class":132}," \"\u002Fmedia\u002F\"\n",[115,15211,15212,15215,15217],{"class":117,"line":175},[115,15213,15214],{"class":202},"MEDIA_ROOT",[115,15216,2380],{"class":121},[115,15218,15219],{"class":132}," \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\"\n",[16,15221,15222],{},"Run a deployment check:",[106,15224,15226],{"className":108,"code":15225,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource .venv\u002Fbin\u002Factivate\npython manage.py check --deploy\n",[20,15227,15228,15234,15240],{"__ignoreMap":111},[115,15229,15230,15232],{"class":117,"line":118},[115,15231,5303],{"class":202},[115,15233,5306],{"class":132},[115,15235,15236,15238],{"class":117,"line":136},[115,15237,5311],{"class":202},[115,15239,12297],{"class":132},[115,15241,15242,15244,15246,15248],{"class":117,"line":149},[115,15243,1114],{"class":262},[115,15245,1117],{"class":132},[115,15247,1814],{"class":132},[115,15249,1817],{"class":202},[16,15251,15252],{},[1226,15253,14174],{},[16,15255,15256],{},"Fix any warnings that are relevant to your setup before continuing.",[11,15258,15260],{"id":15259},"_8-run-release-commands","8) Run release commands",[16,15262,15263],{},"Run checks and release commands from the active release:",[106,15265,15267],{"className":108,"code":15266,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource .venv\u002Fbin\u002Factivate\npython manage.py check --deploy\npython manage.py migrate\npython manage.py collectstatic --noinput\n",[20,15268,15269,15275,15281,15291,15299],{"__ignoreMap":111},[115,15270,15271,15273],{"class":117,"line":118},[115,15272,5303],{"class":202},[115,15274,5306],{"class":132},[115,15276,15277,15279],{"class":117,"line":136},[115,15278,5311],{"class":202},[115,15280,12297],{"class":132},[115,15282,15283,15285,15287,15289],{"class":117,"line":149},[115,15284,1114],{"class":262},[115,15286,1117],{"class":132},[115,15288,1814],{"class":132},[115,15290,1817],{"class":202},[115,15292,15293,15295,15297],{"class":117,"line":162},[115,15294,1114],{"class":262},[115,15296,1117],{"class":132},[115,15298,11324],{"class":132},[115,15300,15301,15303,15305,15307],{"class":117,"line":175},[115,15302,1114],{"class":262},[115,15304,1117],{"class":132},[115,15306,1838],{"class":132},[115,15308,1841],{"class":202},[16,15310,15311],{},"Create a superuser only if you actually need admin access:",[106,15313,15315],{"className":108,"code":15314,"language":110,"meta":111,"style":111},"python manage.py createsuperuser\n",[20,15316,15317],{"__ignoreMap":111},[115,15318,15319,15321,15323],{"class":117,"line":118},[115,15320,1114],{"class":262},[115,15322,1117],{"class":132},[115,15324,15325],{"class":132}," createsuperuser\n",[16,15327,15328],{},"Rollback note: application rollback is easy; database rollback is not. Before risky schema changes, take a database backup and avoid destructive migrations without a recovery plan.",[11,15330,15332],{"id":15331},"_9-configure-gunicorn-with-systemd","9) Configure Gunicorn with systemd",[16,15334,8628,15335,241],{},[20,15336,15337],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-myapp.service",[106,15339,15341],{"className":2026,"code":15340,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for myapp\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp.env\nRuntimeDirectory=gunicorn-myapp\nRuntimeDirectoryMode=0755\nExecStart=\u002Fsrv\u002Fmyapp\u002Fcurrent\u002F.venv\u002Fbin\u002Fgunicorn \\\n    --workers 3 \\\n    --bind unix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock \\\n    config.wsgi:application\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n",[20,15342,15343,15347,15354,15360,15364,15368,15374,15380,15386,15392,15399,15406,15413,15418,15423,15428,15434,15440,15444,15448],{"__ignoreMap":111},[115,15344,15345],{"class":117,"line":118},[115,15346,2035],{"class":262},[115,15348,15349,15351],{"class":117,"line":136},[115,15350,2040],{"class":121},[115,15352,15353],{"class":125},"=Gunicorn for myapp\n",[115,15355,15356,15358],{"class":117,"line":149},[115,15357,2048],{"class":121},[115,15359,2051],{"class":125},[115,15361,15362],{"class":117,"line":162},[115,15363,310],{"emptyLinePlaceholder":309},[115,15365,15366],{"class":117,"line":175},[115,15367,2060],{"class":262},[115,15369,15370,15372],{"class":117,"line":350},[115,15371,2065],{"class":121},[115,15373,12548],{"class":125},[115,15375,15376,15378],{"class":117,"line":365},[115,15377,2073],{"class":121},[115,15379,2076],{"class":125},[115,15381,15382,15384],{"class":117,"line":380},[115,15383,2081],{"class":121},[115,15385,4905],{"class":125},[115,15387,15388,15390],{"class":117,"line":487},[115,15389,2089],{"class":121},[115,15391,2092],{"class":125},[115,15393,15394,15396],{"class":117,"line":2095},[115,15395,2098],{"class":121},[115,15397,15398],{"class":125},"=gunicorn-myapp\n",[115,15400,15401,15403],{"class":117,"line":2104},[115,15402,12580],{"class":121},[115,15404,15405],{"class":125},"=0755\n",[115,15407,15408,15410],{"class":117,"line":2113},[115,15409,2107],{"class":121},[115,15411,15412],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fcurrent\u002F.venv\u002Fbin\u002Fgunicorn \\\n",[115,15414,15415],{"class":117,"line":2122},[115,15416,15417],{"class":125},"    --workers 3 \\\n",[115,15419,15420],{"class":117,"line":2131},[115,15421,15422],{"class":125},"    --bind unix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock \\\n",[115,15424,15425],{"class":117,"line":2136},[115,15426,15427],{"class":125},"    config.wsgi:application\n",[115,15429,15430,15432],{"class":117,"line":2142},[115,15431,2116],{"class":121},[115,15433,4932],{"class":125},[115,15435,15436,15438],{"class":117,"line":2273},[115,15437,2125],{"class":121},[115,15439,2128],{"class":125},[115,15441,15442],{"class":117,"line":2282},[115,15443,310],{"emptyLinePlaceholder":309},[115,15445,15446],{"class":117,"line":2291},[115,15447,2139],{"class":262},[115,15449,15450,15452],{"class":117,"line":2299},[115,15451,2145],{"class":121},[115,15453,2148],{"class":125},[16,15455,15456],{},"Enable and start the service:",[106,15458,15460],{"className":108,"code":15459,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable gunicorn-myapp\nsudo systemctl start gunicorn-myapp\nsudo systemctl status gunicorn-myapp\n",[20,15461,15462,15470,15481,15492],{"__ignoreMap":111},[115,15463,15464,15466,15468],{"class":117,"line":118},[115,15465,2001],{"class":262},[115,15467,3480],{"class":132},[115,15469,4984],{"class":132},[115,15471,15472,15474,15476,15478],{"class":117,"line":136},[115,15473,2001],{"class":262},[115,15475,3480],{"class":132},[115,15477,8567],{"class":132},[115,15479,15480],{"class":132}," gunicorn-myapp\n",[115,15482,15483,15485,15487,15490],{"class":117,"line":149},[115,15484,2001],{"class":262},[115,15486,3480],{"class":132},[115,15488,15489],{"class":132}," start",[115,15491,15480],{"class":132},[115,15493,15494,15496,15498,15500],{"class":117,"line":162},[115,15495,2001],{"class":262},[115,15497,3480],{"class":132},[115,15499,1984],{"class":132},[115,15501,15480],{"class":132},[16,15503,15504],{},"Inspect logs if needed:",[106,15506,15508],{"className":108,"code":15507,"language":110,"meta":111,"style":111},"journalctl -u gunicorn-myapp -n 50 --no-pager\n",[20,15509,15510],{"__ignoreMap":111},[115,15511,15512,15514,15516,15519,15521,15524],{"class":117,"line":118},[115,15513,2785],{"class":262},[115,15515,2788],{"class":202},[115,15517,15518],{"class":132}," gunicorn-myapp",[115,15520,2794],{"class":202},[115,15522,15523],{"class":202}," 50",[115,15525,2800],{"class":202},[16,15527,15528],{},[1226,15529,14174],{},[106,15531,15533],{"className":108,"code":15532,"language":110,"meta":111,"style":111},"sudo ls -l \u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock\n",[20,15534,15535],{"__ignoreMap":111},[115,15536,15537,15539,15541,15543],{"class":117,"line":118},[115,15538,2001],{"class":262},[115,15540,12758],{"class":132},[115,15542,14881],{"class":202},[115,15544,15545],{"class":132}," \u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock\n",[11,15547,15549],{"id":15548},"_10-configure-nginx-as-the-reverse-proxy","10) Configure Nginx as the reverse proxy",[16,15551,8628,15552,241],{},[20,15553,15554],{},"\u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp",[106,15556,15558],{"className":2154,"code":15557,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    client_max_body_size 20M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        include proxy_params;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    }\n}\n",[20,15559,15560,15566,15574,15580,15584,15592,15596,15604,15611,15615,15619,15627,15634,15638,15642,15650,15658,15665,15671,15677,15683,15687],{"__ignoreMap":111},[115,15561,15562,15564],{"class":117,"line":118},[115,15563,2163],{"class":121},[115,15565,2166],{"class":125},[115,15567,15568,15570,15572],{"class":117,"line":136},[115,15569,2171],{"class":121},[115,15571,3808],{"class":202},[115,15573,3811],{"class":125},[115,15575,15576,15578],{"class":117,"line":149},[115,15577,2182],{"class":121},[115,15579,3713],{"class":125},[115,15581,15582],{"class":117,"line":162},[115,15583,310],{"emptyLinePlaceholder":309},[115,15585,15586,15588,15590],{"class":117,"line":175},[115,15587,6987],{"class":121},[115,15589,6990],{"class":202},[115,15591,3811],{"class":125},[115,15593,15594],{"class":117,"line":350},[115,15595,310],{"emptyLinePlaceholder":309},[115,15597,15598,15600,15602],{"class":117,"line":365},[115,15599,2214],{"class":121},[115,15601,2217],{"class":262},[115,15603,2220],{"class":125},[115,15605,15606,15608],{"class":117,"line":380},[115,15607,2225],{"class":121},[115,15609,15610],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n",[115,15612,15613],{"class":117,"line":487},[115,15614,2233],{"class":125},[115,15616,15617],{"class":117,"line":2095},[115,15618,310],{"emptyLinePlaceholder":309},[115,15620,15621,15623,15625],{"class":117,"line":2104},[115,15622,2214],{"class":121},[115,15624,2244],{"class":262},[115,15626,2220],{"class":125},[115,15628,15629,15631],{"class":117,"line":2113},[115,15630,2225],{"class":121},[115,15632,15633],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n",[115,15635,15636],{"class":117,"line":2122},[115,15637,2233],{"class":125},[115,15639,15640],{"class":117,"line":2131},[115,15641,310],{"emptyLinePlaceholder":309},[115,15643,15644,15646,15648],{"class":117,"line":2136},[115,15645,2214],{"class":121},[115,15647,2268],{"class":262},[115,15649,2220],{"class":125},[115,15651,15652,15655],{"class":117,"line":2142},[115,15653,15654],{"class":121},"        include ",[115,15656,15657],{"class":125},"proxy_params;\n",[115,15659,15660,15662],{"class":117,"line":2273},[115,15661,2276],{"class":121},[115,15663,15664],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock;\n",[115,15666,15667,15669],{"class":117,"line":2282},[115,15668,2285],{"class":121},[115,15670,2288],{"class":125},[115,15672,15673,15675],{"class":117,"line":2291},[115,15674,2285],{"class":121},[115,15676,2304],{"class":125},[115,15678,15679,15681],{"class":117,"line":2299},[115,15680,2285],{"class":121},[115,15682,2312],{"class":125},[115,15684,15685],{"class":117,"line":2307},[115,15686,2233],{"class":125},[115,15688,15689],{"class":117,"line":2315},[115,15690,2323],{"class":125},[16,15692,15693],{},"Enable the site:",[106,15695,15697],{"className":108,"code":15696,"language":110,"meta":111,"style":111},"sudo ln -s \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp \u002Fetc\u002Fnginx\u002Fsites-enabled\u002F\nsudo rm -f \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,15698,15699,15713,15725,15733],{"__ignoreMap":111},[115,15700,15701,15703,15705,15707,15710],{"class":117,"line":118},[115,15702,2001],{"class":262},[115,15704,13105],{"class":132},[115,15706,549],{"class":202},[115,15708,15709],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp",[115,15711,15712],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002F\n",[115,15714,15715,15717,15720,15722],{"class":117,"line":136},[115,15716,2001],{"class":262},[115,15718,15719],{"class":132}," rm",[115,15721,2777],{"class":202},[115,15723,15724],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\n",[115,15726,15727,15729,15731],{"class":117,"line":149},[115,15728,2001],{"class":262},[115,15730,3906],{"class":132},[115,15732,4282],{"class":202},[115,15734,15735,15737,15739,15741],{"class":117,"line":162},[115,15736,2001],{"class":262},[115,15738,3480],{"class":132},[115,15740,3919],{"class":132},[115,15742,1996],{"class":132},[16,15744,15745],{},[1226,15746,14174],{},[106,15748,15750],{"className":108,"code":15749,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\nsystemctl status nginx\n",[20,15751,15752,15760],{"__ignoreMap":111},[115,15753,15754,15756,15758],{"class":117,"line":118},[115,15755,2764],{"class":262},[115,15757,2767],{"class":202},[115,15759,6494],{"class":132},[115,15761,15762,15764,15766],{"class":117,"line":136},[115,15763,1981],{"class":262},[115,15765,1984],{"class":132},[115,15767,1996],{"class":132},[16,15769,15770],{},"If you get a 502 here, check socket permissions, Gunicorn status, and the Nginx error log before moving on.",[11,15772,15774],{"id":15773},"_11-enable-https","11) Enable HTTPS",[16,15776,15777],{},"Install Certbot:",[106,15779,15781],{"className":108,"code":15780,"language":110,"meta":111,"style":111},"sudo apt install -y certbot python3-certbot-nginx\nsudo certbot --nginx -d example.com -d www.example.com\n",[20,15782,15783,15797],{"__ignoreMap":111},[115,15784,15785,15787,15789,15791,15793,15795],{"class":117,"line":118},[115,15786,2001],{"class":262},[115,15788,6588],{"class":132},[115,15790,6600],{"class":132},[115,15792,8432],{"class":202},[115,15794,6603],{"class":132},[115,15796,6606],{"class":132},[115,15798,15799,15801,15803,15805,15807,15809,15811],{"class":117,"line":136},[115,15800,2001],{"class":262},[115,15802,6603],{"class":132},[115,15804,6687],{"class":202},[115,15806,1019],{"class":202},[115,15808,6434],{"class":132},[115,15810,1019],{"class":202},[115,15812,6696],{"class":132},[16,15814,15815],{},"This can update the Nginx config and add HTTP-to-HTTPS redirects.",[16,15817,15818],{},"Verify renewal:",[106,15820,15822],{"className":108,"code":15821,"language":110,"meta":111,"style":111},"systemctl list-timers | grep certbot\nsudo certbot renew --dry-run\n",[20,15823,15824,15836],{"__ignoreMap":111},[115,15825,15826,15828,15830,15832,15834],{"class":117,"line":118},[115,15827,1981],{"class":262},[115,15829,7783],{"class":132},[115,15831,579],{"class":121},[115,15833,4838],{"class":262},[115,15835,7790],{"class":132},[115,15837,15838,15840,15842,15844],{"class":117,"line":136},[115,15839,2001],{"class":262},[115,15841,6603],{"class":132},[115,15843,7816],{"class":132},[115,15845,7819],{"class":202},[16,15847,15848,15849,15851],{},"Once HTTPS is working correctly, enable or keep ",[20,15850,13296],{}," in Django and reload your app service if needed.",[16,15853,15854],{},"Then confirm Django sees secure requests correctly by testing login, forms, and redirects over HTTPS.",[16,15856,15857],{},[1226,15858,14174],{},[106,15860,15861],{"className":108,"code":13378,"language":110,"meta":111,"style":111},[20,15862,15863],{"__ignoreMap":111},[115,15864,15865,15867,15869],{"class":117,"line":118},[115,15866,2764],{"class":262},[115,15868,2767],{"class":202},[115,15870,2770],{"class":132},[11,15872,15874],{"id":15873},"_12-verify-the-deployment-end-to-end","12) Verify the deployment end to end",[16,15876,15877],{},"Functional checks:",[63,15879,15880,15883,15886,15889,15892],{},[66,15881,15882],{},"homepage loads",[66,15884,15885],{},"admin loads if enabled",[66,15887,15888],{},"static assets return 200",[66,15890,15891],{},"forms submit without CSRF errors",[66,15893,15894],{},"database-backed pages work",[16,15896,15897],{},"Service checks:",[106,15899,15901],{"className":108,"code":15900,"language":110,"meta":111,"style":111},"systemctl status gunicorn-myapp\nsystemctl status nginx\nsudo ss -ltnp\njournalctl -u gunicorn-myapp -n 50 --no-pager\nsudo nginx -t\n",[20,15902,15903,15911,15919,15928,15942],{"__ignoreMap":111},[115,15904,15905,15907,15909],{"class":117,"line":118},[115,15906,1981],{"class":262},[115,15908,1984],{"class":132},[115,15910,15480],{"class":132},[115,15912,15913,15915,15917],{"class":117,"line":136},[115,15914,1981],{"class":262},[115,15916,1984],{"class":132},[115,15918,1996],{"class":132},[115,15920,15921,15923,15925],{"class":117,"line":149},[115,15922,2001],{"class":262},[115,15924,2004],{"class":132},[115,15926,15927],{"class":202}," -ltnp\n",[115,15929,15930,15932,15934,15936,15938,15940],{"class":117,"line":162},[115,15931,2785],{"class":262},[115,15933,2788],{"class":202},[115,15935,15518],{"class":132},[115,15937,2794],{"class":202},[115,15939,15523],{"class":202},[115,15941,2800],{"class":202},[115,15943,15944,15946,15948],{"class":117,"line":175},[115,15945,2001],{"class":262},[115,15947,3906],{"class":132},[115,15949,4282],{"class":202},[16,15951,1132],{},[63,15953,15954,15958,15963,15966],{},[66,15955,15956],{},[20,15957,2707],{},[66,15959,15960,15962],{},[20,15961,2719],{}," matches the real domain",[66,15964,15965],{},"no secrets are stored in Git",[66,15967,15968,15971],{},[20,15969,15970],{},"python manage.py check --deploy"," is clean or understood",[11,15973,15975],{"id":15974},"_13-rollback-and-recovery-plan","13) Rollback and recovery plan",[16,15977,15978],{},"Keep the previous release directory. To roll back code:",[106,15980,15982],{"className":108,"code":15981,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002Fprevious-release \u002Fsrv\u002Fmyapp\u002Fcurrent\nsudo systemctl restart gunicorn-myapp\n",[20,15983,15984,15995],{"__ignoreMap":111},[115,15985,15986,15988,15990,15993],{"class":117,"line":118},[115,15987,14854],{"class":262},[115,15989,14857],{"class":202},[115,15991,15992],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002Fprevious-release",[115,15994,5306],{"class":132},[115,15996,15997,15999,16001,16003],{"class":117,"line":136},[115,15998,2001],{"class":262},[115,16000,3480],{"class":132},[115,16002,3483],{"class":132},[115,16004,15480],{"class":132},[16,16006,16007],{},"Then verify the site and logs again.",[16,16009,16010],{},"Database rollback is harder. If a migration changed schema incompatibly, switching code back may not be enough. Before risky releases, back up the database and uploaded media.",[52,16012,4319],{"id":4318},[16,16014,16015],{},"Once you have repeated this process more than a few times, the mechanical parts should become a script or template: package install, directory setup, env file placement, systemd unit creation, Nginx config, release switching, and post-deploy checks. Manual setup is useful first because it makes failures easier to understand. After that, automation reduces drift and rollback mistakes.",[11,16017,1321],{"id":1320},[16,16019,16020],{},"Gunicorn + Nginx + systemd is a strong baseline for deploying Django on AWS EC2 because it is simple, observable, and widely used.",[63,16022,16023,16026,16029],{},[66,16024,16025],{},"Gunicorn runs the Django app process",[66,16027,16028],{},"Nginx handles client connections, static files, and proxying",[66,16030,16031],{},"systemd restarts Gunicorn on failure and starts it on boot",[16,16033,16034],{},"This stack is a good default for a single EC2 instance. Move beyond it when you need one or more of:",[63,16036,16037,16040,16043,16046,16049],{},[66,16038,16039],{},"higher traffic requiring horizontal scaling",[66,16041,16042],{},"zero-downtime deploys",[66,16044,16045],{},"separate worker services",[66,16047,16048],{},"managed PostgreSQL and Redis",[66,16050,16051],{},"a load balancer in front of multiple app nodes",[11,16053,10095],{"id":10094},[16,16055,16056],{},"Common EC2 deployment issues:",[63,16058,16059,16069,16072,16075,16080,16085,16093,16099],{},[66,16060,16061,16062,16064,16065,4493,16067],{},"security group allows ",[20,16063,10241],{}," but not ",[20,16066,3808],{},[20,16068,2174],{},[66,16070,16071],{},"Nginx cannot access the Gunicorn socket due to permission mismatch",[66,16073,16074],{},"DNS still points to an old IP",[66,16076,16077,16079],{},[20,16078,2719],{}," does not include the final hostname",[66,16081,16082,16084],{},[20,16083,13689],{}," ran into the wrong path",[66,16086,16087,16089,16090],{},[20,16088,2725],{}," is missing ",[20,16091,16092],{},"https:\u002F\u002F...",[66,16094,16095,16096,16098],{},"redirect loops happen if ",[20,16097,2377],{}," is missing behind TLS termination",[66,16100,16101,16103],{},[20,16102,2407],{}," was enabled before HTTPS was actually working",[16,16105,16106],{},"If your app stores user uploads locally, remember that instance replacement or disk failure can lose them unless you back up media. For larger apps, object storage is often safer than local disk.",[16,16108,16109],{},"This guide is intentionally non-Docker. If your stack is container-based, use a separate deployment path rather than mixing host-level and container-level process management.",[11,16111,1386],{"id":1385},[16,16113,16114,16115,16119],{},"Before deploying, use the ",[1226,16116,16117],{},[1395,16118,3000],{"href":2999}," to confirm settings, secrets, and backup readiness.",[16,16121,16122,16123,211],{},"If you want more detail on the app server and reverse proxy side, see ",[1226,16124,16125],{},[1395,16126,2986],{"href":2985},[16,16128,16129,16130,211],{},"If your project is ASGI-based, read ",[1226,16131,16132],{},[1395,16133,8039],{"href":8038},[16,16135,16136,16137,211],{},"If you want a simpler HTTPS setup on a small server, see ",[1226,16138,16139],{},[1395,16140,8046],{"href":8045},[11,16142,1420],{"id":1419},[52,16144,16146],{"id":16145},"how-much-ec2-do-i-need-for-a-small-django-app","How much EC2 do I need for a small Django app?",[16,16148,16149,16150,16152],{},"For a small internal app or low-traffic site, ",[20,16151,14140],{}," is often enough to start. Watch memory, swap use, response times, and database load before deciding whether to move to a larger instance.",[52,16154,16156],{"id":16155},"should-i-use-sqlite-on-ec2-for-production","Should I use SQLite on EC2 for production?",[16,16158,16159],{},"Usually no. SQLite can work for very small single-process workloads, but PostgreSQL is the normal production choice for Django on EC2 because it handles concurrency, backups, and operational growth much better.",[52,16161,16163],{"id":16162},"can-i-deploy-django-on-ec2-without-nginx","Can I deploy Django on EC2 without Nginx?",[16,16165,16166],{},"Yes, but it is usually not the best production choice. Nginx gives you better static file handling, TLS integration, buffering, and a stable reverse proxy layer in front of Gunicorn.",[52,16168,16170],{"id":16169},"how-do-i-update-the-app-after-the-first-deployment","How do I update the app after the first deployment?",[16,16172,16173,16174,3146,16176,16178,16179,16181],{},"Create a new release directory, install dependencies in that release, run checks, run ",[20,16175,10296],{},[20,16177,13689],{},", switch the ",[20,16180,14814],{}," symlink, and restart Gunicorn. Keep the previous release intact so you can roll back code quickly if needed.",[52,16183,16185],{"id":16184},"what-should-i-back-up-on-a-single-server-ec2-deployment","What should I back up on a single-server EC2 deployment?",[16,16187,10908],{},[63,16189,16190,16193,16196,16199],{},[66,16191,16192],{},"PostgreSQL database",[66,16194,16195],{},"uploaded media files if stored locally",[66,16197,16198],{},"critical config such as env files and Nginx\u002Fsystemd definitions",[66,16200,16201],{},"EBS snapshots if you rely on local state",[16,16203,16204],{},"For schema-changing releases, take a fresh database backup before running migrations.",[1485,16206,16207],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":111,"searchDepth":149,"depth":149,"links":16209},[16210,16211,16212,16213,16214,16215,16216,16217,16218,16219,16220,16221,16222,16223,16224,16225,16228,16229,16230,16231],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":14133,"depth":136,"text":14134},{"id":14196,"depth":136,"text":14197},{"id":14452,"depth":136,"text":14453},{"id":14577,"depth":136,"text":14578},{"id":14757,"depth":136,"text":14758},{"id":14886,"depth":136,"text":14887},{"id":14972,"depth":136,"text":14973},{"id":15259,"depth":136,"text":15260},{"id":15331,"depth":136,"text":15332},{"id":15548,"depth":136,"text":15549},{"id":15773,"depth":136,"text":15774},{"id":15873,"depth":136,"text":15874},{"id":15974,"depth":136,"text":15975,"children":16226},[16227],{"id":4318,"depth":149,"text":4319},{"id":1320,"depth":136,"text":1321},{"id":10094,"depth":136,"text":10095},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":16232},[16233,16234,16235,16236,16237],{"id":16145,"depth":149,"text":16146},{"id":16155,"depth":149,"text":16156},{"id":16162,"depth":149,"text":16163},{"id":16169,"depth":149,"text":16170},{"id":16184,"depth":149,"text":16185},"To deploy Django on AWS EC2 reliably, you need more than python manage.py runserver.",{},"\u002Fdeploy-django-on-aws-ec2","10",[2985,14027,16243],"\u002Fdeploy\u002Fdjango-deploy-with-ansible",{"title":14035,"description":16238},[1557,16246,14954,2156],"aws-ec2","deploy-django-on-aws-ec2",[1557,16246,14954,2156],"Rvy4znl36AlNSPoA8P1jzeC4C-qrR4XTL3wiCx7mw4U",{"id":16251,"title":16252,"body":16253,"category":3088,"description":18001,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":18002,"navigation":309,"path":18003,"priority":18004,"related":18005,"role":1553,"section":3098,"seo":18006,"stack":18007,"stem":18009,"tags":18010,"type":1561,"__hash__":18011},"articles\u002Fdeploy-django-on-fly-io.md","Deploy Django on Fly.io with Postgres",{"type":8,"value":16254,"toc":17973},[16255,16257,16267,16270,16272,16283,16286,16288,16292,16295,16328,16331,16754,16762,16764,16783,16789,16797,16801,16804,16807,16820,16823,16827,16830,16972,16975,16988,16991,17008,17011,17031,17036,17040,17043,17059,17062,17074,17077,17088,17095,17101,17200,17203,17221,17224,17322,17329,17333,17336,17350,17353,17356,17381,17387,17390,17415,17418,17431,17434,17440,17444,17447,17450,17454,17457,17485,17488,17507,17513,17515,17528,17532,17535,17546,17549,17560,17563,17566,17570,17573,17602,17605,17608,17635,17638,17653,17656,17671,17675,17678,17696,17699,17731,17734,17757,17760,17776,17779,17781,17784,17804,17807,17811,17817,17819,17906,17908,17913,17919,17924,17930,17932,17936,17939,17943,17946,17950,17956,17960,17963,17967,17970],[11,16256,14],{"id":13},[16,16258,16259,16260,3146,16263,16266],{},"A production-safe Fly.io Django deployment needs more than ",[20,16261,16262],{},"fly launch",[20,16264,16265],{},"fly deploy",". You need Django production settings, a real PostgreSQL connection, secrets stored outside the repo, static files handled correctly, migrations applied in a controlled way, and a rollback plan that accounts for database schema changes.",[16,16268,16269],{},"If you skip those pieces, the app may boot but still fail in production with host header errors, CSRF failures, missing static files, broken HTTPS handling, or a release that cannot be rolled back cleanly after migrations.",[11,16271,30],{"id":29},[16,16273,16274,16275,16278,16279,16282],{},"To deploy Django on Fly.io with Postgres safely, package the app with Docker, run Django behind Gunicorn, provision a separate Fly Postgres service, attach it to your app, store sensitive settings with ",[20,16276,16277],{},"fly secrets set",", serve static files with WhiteNoise or another production asset strategy, and run ",[20,16280,16281],{},"manage.py migrate --noinput"," in Fly’s release phase.",[16,16284,16285],{},"After deploy, verify health checks, logs, HTTPS behavior, static files, and one database-backed request. For rollback, redeploy a previous image only if your schema changes are backward compatible with the older code.",[11,16287,43],{"id":42},[11,16289,16291],{"id":16290},"_1-prepare-the-django-app-for-flyio","1) Prepare the Django app for Fly.io",[16,16293,16294],{},"Install the core dependencies:",[106,16296,16298],{"className":108,"code":16297,"language":110,"meta":111,"style":111},"pip install gunicorn psycopg[binary] whitenoise dj-database-url\npip freeze > requirements.txt\n",[20,16299,16300,16317],{"__ignoreMap":111},[115,16301,16302,16304,16306,16308,16311,16314],{"class":117,"line":118},[115,16303,8618],{"class":262},[115,16305,6600],{"class":132},[115,16307,2791],{"class":132},[115,16309,16310],{"class":132}," psycopg[binary]",[115,16312,16313],{"class":132}," whitenoise",[115,16315,16316],{"class":132}," dj-database-url\n",[115,16318,16319,16321,16324,16326],{"class":117,"line":136},[115,16320,8618],{"class":262},[115,16322,16323],{"class":132}," freeze",[115,16325,604],{"class":121},[115,16327,12353],{"class":132},[16,16329,16330],{},"Use production settings that read from environment variables:",[106,16332,16334],{"className":2369,"code":16333,"language":1114,"meta":111,"style":111},"# project\u002Fsettings\u002Fproduction.py\nimport os\nfrom pathlib import Path\nimport dj_database_url\n\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\nDEBUG = False\n\nSECRET_KEY = os.environ[\"SECRET_KEY\"]\n\nALLOWED_HOSTS = [\n    \"your-app-name.fly.dev\",\n    \"www.example.com\",\n    \"example.com\",\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fyour-app-name.fly.dev\",\n    \"https:\u002F\u002Fwww.example.com\",\n    \"https:\u002F\u002Fexample.com\",\n]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nUSE_X_FORWARDED_HOST = True\n\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\n# Roll out HSTS only after HTTPS works correctly on every domain.\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # ...\n]\n\nSTORAGES = {\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    }\n}\n\nDATABASES = {\n    \"default\": dj_database_url.config(\n        env=\"DATABASE_URL\",\n        conn_max_age=600,\n    )\n}\n",[20,16335,16336,16341,16347,16359,16366,16370,16386,16390,16398,16402,16415,16419,16427,16434,16440,16446,16450,16454,16462,16469,16475,16481,16485,16489,16505,16513,16517,16525,16533,16542,16547,16553,16562,16571,16580,16585,16594,16607,16612,16622,16630,16638,16644,16649,16654,16664,16672,16685,16690,16695,16700,16709,16717,16730,16743,16749],{"__ignoreMap":111},[115,16337,16338],{"class":117,"line":118},[115,16339,16340],{"class":3861},"# project\u002Fsettings\u002Fproduction.py\n",[115,16342,16343,16345],{"class":117,"line":136},[115,16344,5613],{"class":121},[115,16346,5616],{"class":125},[115,16348,16349,16351,16354,16356],{"class":117,"line":149},[115,16350,5621],{"class":121},[115,16352,16353],{"class":125}," pathlib ",[115,16355,5613],{"class":121},[115,16357,16358],{"class":125}," Path\n",[115,16360,16361,16363],{"class":117,"line":162},[115,16362,5613],{"class":121},[115,16364,16365],{"class":125}," dj_database_url\n",[115,16367,16368],{"class":117,"line":175},[115,16369,310],{"emptyLinePlaceholder":309},[115,16371,16372,16375,16377,16380,16383],{"class":117,"line":350},[115,16373,16374],{"class":202},"BASE_DIR",[115,16376,2380],{"class":121},[115,16378,16379],{"class":125}," Path(",[115,16381,16382],{"class":202},"__file__",[115,16384,16385],{"class":125},").resolve().parent.parent.parent\n",[115,16387,16388],{"class":117,"line":365},[115,16389,310],{"emptyLinePlaceholder":309},[115,16391,16392,16394,16396],{"class":117,"line":380},[115,16393,7350],{"class":202},[115,16395,2380],{"class":121},[115,16397,7355],{"class":202},[115,16399,16400],{"class":117,"line":487},[115,16401,310],{"emptyLinePlaceholder":309},[115,16403,16404,16406,16408,16410,16413],{"class":117,"line":2095},[115,16405,2713],{"class":202},[115,16407,2380],{"class":121},[115,16409,8861],{"class":125},[115,16411,16412],{"class":132},"\"SECRET_KEY\"",[115,16414,2552],{"class":125},[115,16416,16417],{"class":117,"line":2104},[115,16418,310],{"emptyLinePlaceholder":309},[115,16420,16421,16423,16425],{"class":117,"line":2113},[115,16422,2719],{"class":202},[115,16424,2380],{"class":121},[115,16426,3540],{"class":125},[115,16428,16429,16432],{"class":117,"line":2122},[115,16430,16431],{"class":132},"    \"your-app-name.fly.dev\"",[115,16433,3354],{"class":125},[115,16435,16436,16438],{"class":117,"line":2131},[115,16437,3552],{"class":132},[115,16439,3354],{"class":125},[115,16441,16442,16444],{"class":117,"line":2136},[115,16443,3545],{"class":132},[115,16445,3354],{"class":125},[115,16447,16448],{"class":117,"line":2142},[115,16449,2552],{"class":125},[115,16451,16452],{"class":117,"line":2273},[115,16453,310],{"emptyLinePlaceholder":309},[115,16455,16456,16458,16460],{"class":117,"line":2282},[115,16457,2725],{"class":202},[115,16459,2380],{"class":121},[115,16461,3540],{"class":125},[115,16463,16464,16467],{"class":117,"line":2291},[115,16465,16466],{"class":132},"    \"https:\u002F\u002Fyour-app-name.fly.dev\"",[115,16468,3354],{"class":125},[115,16470,16471,16473],{"class":117,"line":2299},[115,16472,3589],{"class":132},[115,16474,3354],{"class":125},[115,16476,16477,16479],{"class":117,"line":2307},[115,16478,3582],{"class":132},[115,16480,3354],{"class":125},[115,16482,16483],{"class":117,"line":2315},[115,16484,2552],{"class":125},[115,16486,16487],{"class":117,"line":2320},[115,16488,310],{"emptyLinePlaceholder":309},[115,16490,16491,16493,16495,16497,16499,16501,16503],{"class":117,"line":7083},[115,16492,2377],{"class":202},[115,16494,2380],{"class":121},[115,16496,2383],{"class":125},[115,16498,2386],{"class":132},[115,16500,1153],{"class":125},[115,16502,2391],{"class":132},[115,16504,2394],{"class":125},[115,16506,16507,16509,16511],{"class":117,"line":7090},[115,16508,12021],{"class":202},[115,16510,2380],{"class":121},[115,16512,2412],{"class":202},[115,16514,16515],{"class":117,"line":7097},[115,16516,310],{"emptyLinePlaceholder":309},[115,16518,16519,16521,16523],{"class":117,"line":7108},[115,16520,2407],{"class":202},[115,16522,2380],{"class":121},[115,16524,2412],{"class":202},[115,16526,16527,16529,16531],{"class":117,"line":7113},[115,16528,2417],{"class":202},[115,16530,2380],{"class":121},[115,16532,2412],{"class":202},[115,16534,16536,16538,16540],{"class":117,"line":16535},29,[115,16537,2426],{"class":202},[115,16539,2380],{"class":121},[115,16541,2412],{"class":202},[115,16543,16545],{"class":117,"line":16544},30,[115,16546,310],{"emptyLinePlaceholder":309},[115,16548,16550],{"class":117,"line":16549},31,[115,16551,16552],{"class":3861},"# Roll out HSTS only after HTTPS works correctly on every domain.\n",[115,16554,16556,16558,16560],{"class":117,"line":16555},32,[115,16557,7440],{"class":202},[115,16559,2380],{"class":121},[115,16561,11991],{"class":202},[115,16563,16565,16567,16569],{"class":117,"line":16564},33,[115,16566,7464],{"class":202},[115,16568,2380],{"class":121},[115,16570,2412],{"class":202},[115,16572,16574,16576,16578],{"class":117,"line":16573},34,[115,16575,12004],{"class":202},[115,16577,2380],{"class":121},[115,16579,7355],{"class":202},[115,16581,16583],{"class":117,"line":16582},35,[115,16584,310],{"emptyLinePlaceholder":309},[115,16586,16588,16590,16592],{"class":117,"line":16587},36,[115,16589,11908],{"class":202},[115,16591,2380],{"class":121},[115,16593,11913],{"class":132},[115,16595,16597,16599,16601,16603,16605],{"class":117,"line":16596},37,[115,16598,11918],{"class":202},[115,16600,2380],{"class":121},[115,16602,11923],{"class":202},[115,16604,11926],{"class":121},[115,16606,11929],{"class":132},[115,16608,16610],{"class":117,"line":16609},38,[115,16611,310],{"emptyLinePlaceholder":309},[115,16613,16615,16618,16620],{"class":117,"line":16614},39,[115,16616,16617],{"class":202},"MIDDLEWARE",[115,16619,2380],{"class":121},[115,16621,3540],{"class":125},[115,16623,16625,16628],{"class":117,"line":16624},40,[115,16626,16627],{"class":132},"    \"django.middleware.security.SecurityMiddleware\"",[115,16629,3354],{"class":125},[115,16631,16633,16636],{"class":117,"line":16632},41,[115,16634,16635],{"class":132},"    \"whitenoise.middleware.WhiteNoiseMiddleware\"",[115,16637,3354],{"class":125},[115,16639,16641],{"class":117,"line":16640},42,[115,16642,16643],{"class":3861},"    # ...\n",[115,16645,16647],{"class":117,"line":16646},43,[115,16648,2552],{"class":125},[115,16650,16652],{"class":117,"line":16651},44,[115,16653,310],{"emptyLinePlaceholder":309},[115,16655,16657,16660,16662],{"class":117,"line":16656},45,[115,16658,16659],{"class":202},"STORAGES",[115,16661,2380],{"class":121},[115,16663,2166],{"class":125},[115,16665,16667,16670],{"class":117,"line":16666},46,[115,16668,16669],{"class":132},"    \"staticfiles\"",[115,16671,3374],{"class":125},[115,16673,16675,16678,16680,16683],{"class":117,"line":16674},47,[115,16676,16677],{"class":132},"        \"BACKEND\"",[115,16679,2513],{"class":125},[115,16681,16682],{"class":132},"\"whitenoise.storage.CompressedManifestStaticFilesStorage\"",[115,16684,3354],{"class":125},[115,16686,16688],{"class":117,"line":16687},48,[115,16689,2233],{"class":125},[115,16691,16693],{"class":117,"line":16692},49,[115,16694,2323],{"class":125},[115,16696,16698],{"class":117,"line":16697},50,[115,16699,310],{"emptyLinePlaceholder":309},[115,16701,16703,16705,16707],{"class":117,"line":16702},51,[115,16704,10632],{"class":202},[115,16706,2380],{"class":121},[115,16708,2166],{"class":125},[115,16710,16712,16714],{"class":117,"line":16711},52,[115,16713,10664],{"class":132},[115,16715,16716],{"class":125},": dj_database_url.config(\n",[115,16718,16720,16723,16725,16728],{"class":117,"line":16719},53,[115,16721,16722],{"class":5680},"        env",[115,16724,129],{"class":121},[115,16726,16727],{"class":132},"\"DATABASE_URL\"",[115,16729,3354],{"class":125},[115,16731,16733,16736,16738,16741],{"class":117,"line":16732},54,[115,16734,16735],{"class":5680},"        conn_max_age",[115,16737,129],{"class":121},[115,16739,16740],{"class":202},"600",[115,16742,3354],{"class":125},[115,16744,16746],{"class":117,"line":16745},55,[115,16747,16748],{"class":125},"    )\n",[115,16750,16752],{"class":117,"line":16751},56,[115,16753,2323],{"class":125},[16,16755,12356,16756,16758,16759,211],{},[20,16757,5074],{}," points to the full Python path of your production settings module at deploy time, for example ",[20,16760,16761],{},"project.settings.production",[16,16763,3515],{},[106,16765,16767],{"className":108,"code":16766,"language":110,"meta":111,"style":111},"python manage.py check --deploy --settings=project.settings.production\n",[20,16768,16769],{"__ignoreMap":111},[115,16770,16771,16773,16775,16777,16780],{"class":117,"line":118},[115,16772,1114],{"class":262},[115,16774,1117],{"class":132},[115,16776,1814],{"class":132},[115,16778,16779],{"class":202}," --deploy",[115,16781,16782],{"class":202}," --settings=project.settings.production\n",[16,16784,16785,16786,16788],{},"Fix every important error before you deploy. Warnings may still be acceptable depending on your app, but do not ignore ",[20,16787,2719],{},", HTTPS, secret handling, or proxy-related settings.",[16,16790,16791,16792,3146,16794,16796],{},"Also make sure any custom domains are added to both ",[20,16793,2719],{},[20,16795,2725],{}," before cutover.",[11,16798,16800],{"id":16799},"_2-configure-static-files-for-production","2) Configure static files for production",[16,16802,16803],{},"Fly Machines have ephemeral local filesystems. That means runtime-generated files stored on local disk are not durable unless you explicitly design around volumes, and that is usually the wrong choice for Django user uploads.",[16,16805,16806],{},"For static assets, WhiteNoise is a practical default for small to medium deployments. It works well when static files are collected during build and served by Django\u002FGunicorn.",[16,16808,16809,16810,16812,16813,16816,16817,16819],{},"A common problem here is build-time settings import. If your production settings require runtime-only secrets, ",[20,16811,13689],{}," can fail during ",[20,16814,16815],{},"docker build",". Keep static collection independent from production-only values where possible, and do not make ",[20,16818,13689],{}," depend on a live database connection.",[16,16821,16822],{},"If you also need user-uploaded media, use object storage rather than the local filesystem.",[11,16824,16826],{"id":16825},"_3-create-a-dockerfile","3) Create a Dockerfile",[16,16828,16829],{},"Use a Dockerfile that installs dependencies, copies the app, collects static files, and runs Gunicorn:",[106,16831,16835],{"className":16832,"code":16833,"language":16834,"meta":111,"style":111},"language-dockerfile shiki shiki-themes github-light github-dark","FROM python:3.12-slim\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\nENV DJANGO_SETTINGS_MODULE=project.settings.production\nENV SECRET_KEY=build-only-not-for-runtime\n\nWORKDIR \u002Fapp\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nRUN python manage.py collectstatic --noinput\n\nCMD [\"gunicorn\", \"--bind\", \"0.0.0.0:8000\", \"--workers\", \"2\", \"project.wsgi:application\"]\n","dockerfile",[20,16836,16837,16844,16848,16856,16863,16870,16877,16881,16889,16893,16901,16909,16913,16920,16924,16931,16935],{"__ignoreMap":111},[115,16838,16839,16841],{"class":117,"line":118},[115,16840,11089],{"class":121},[115,16842,16843],{"class":125}," python:3.12-slim\n",[115,16845,16846],{"class":117,"line":136},[115,16847,310],{"emptyLinePlaceholder":309},[115,16849,16850,16853],{"class":117,"line":149},[115,16851,16852],{"class":121},"ENV",[115,16854,16855],{"class":125}," PYTHONDONTWRITEBYTECODE=1\n",[115,16857,16858,16860],{"class":117,"line":162},[115,16859,16852],{"class":121},[115,16861,16862],{"class":125}," PYTHONUNBUFFERED=1\n",[115,16864,16865,16867],{"class":117,"line":175},[115,16866,16852],{"class":121},[115,16868,16869],{"class":125}," DJANGO_SETTINGS_MODULE=project.settings.production\n",[115,16871,16872,16874],{"class":117,"line":350},[115,16873,16852],{"class":121},[115,16875,16876],{"class":125}," SECRET_KEY=build-only-not-for-runtime\n",[115,16878,16879],{"class":117,"line":365},[115,16880,310],{"emptyLinePlaceholder":309},[115,16882,16883,16886],{"class":117,"line":380},[115,16884,16885],{"class":121},"WORKDIR",[115,16887,16888],{"class":125}," \u002Fapp\n",[115,16890,16891],{"class":117,"line":487},[115,16892,310],{"emptyLinePlaceholder":309},[115,16894,16895,16898],{"class":117,"line":2095},[115,16896,16897],{"class":121},"COPY",[115,16899,16900],{"class":125}," requirements.txt .\n",[115,16902,16903,16906],{"class":117,"line":2104},[115,16904,16905],{"class":121},"RUN",[115,16907,16908],{"class":125}," pip install --no-cache-dir -r requirements.txt\n",[115,16910,16911],{"class":117,"line":2113},[115,16912,310],{"emptyLinePlaceholder":309},[115,16914,16915,16917],{"class":117,"line":2122},[115,16916,16897],{"class":121},[115,16918,16919],{"class":125}," . .\n",[115,16921,16922],{"class":117,"line":2131},[115,16923,310],{"emptyLinePlaceholder":309},[115,16925,16926,16928],{"class":117,"line":2136},[115,16927,16905],{"class":121},[115,16929,16930],{"class":125}," python manage.py collectstatic --noinput\n",[115,16932,16933],{"class":117,"line":2142},[115,16934,310],{"emptyLinePlaceholder":309},[115,16936,16937,16940,16942,16945,16947,16950,16952,16955,16957,16960,16962,16965,16967,16970],{"class":117,"line":2273},[115,16938,16939],{"class":121},"CMD",[115,16941,7493],{"class":125},[115,16943,16944],{"class":132},"\"gunicorn\"",[115,16946,1153],{"class":125},[115,16948,16949],{"class":132},"\"--bind\"",[115,16951,1153],{"class":125},[115,16953,16954],{"class":132},"\"0.0.0.0:8000\"",[115,16956,1153],{"class":125},[115,16958,16959],{"class":132},"\"--workers\"",[115,16961,1153],{"class":125},[115,16963,16964],{"class":132},"\"2\"",[115,16966,1153],{"class":125},[115,16968,16969],{"class":132},"\"project.wsgi:application\"",[115,16971,2552],{"class":125},[16,16973,16974],{},"Replace:",[63,16976,16977,16982],{},[66,16978,16979,16981],{},[20,16980,16761],{}," with your real settings module path",[66,16983,16984,16987],{},[20,16985,16986],{},"project.wsgi:application"," with your real WSGI path",[16,16989,16990],{},"Important notes:",[63,16992,16993,17002],{},[66,16994,16995,16996,16998,16999,17001],{},"The build-time ",[20,16997,2713],{}," above is only there so settings can import during image build. Set the real ",[20,17000,2713],{}," later with Fly secrets.",[66,17003,17004,17005,17007],{},"If your settings import code touches the database at import time, fix that first. ",[20,17006,13689],{}," should not require a live database connection.",[16,17009,17010],{},"Verification check: build locally first.",[106,17012,17014],{"className":108,"code":17013,"language":110,"meta":111,"style":111},"docker build -t django-fly-test .\n",[20,17015,17016],{"__ignoreMap":111},[115,17017,17018,17020,17023,17025,17028],{"class":117,"line":118},[115,17019,3295],{"class":262},[115,17021,17022],{"class":132}," build",[115,17024,3909],{"class":202},[115,17026,17027],{"class":132}," django-fly-test",[115,17029,17030],{"class":132}," .\n",[16,17032,6168,17033,17035],{},[20,17034,13689],{}," fails during the build, fix that before using Fly.",[11,17037,17039],{"id":17038},"_4-create-the-flyio-app","4) Create the Fly.io app",[16,17041,17042],{},"Authenticate if needed:",[106,17044,17046],{"className":108,"code":17045,"language":110,"meta":111,"style":111},"fly auth login\n",[20,17047,17048],{"__ignoreMap":111},[115,17049,17050,17053,17056],{"class":117,"line":118},[115,17051,17052],{"class":262},"fly",[115,17054,17055],{"class":132}," auth",[115,17057,17058],{"class":132}," login\n",[16,17060,17061],{},"Initialize the app:",[106,17063,17065],{"className":108,"code":17064,"language":110,"meta":111,"style":111},"fly launch\n",[20,17066,17067],{"__ignoreMap":111},[115,17068,17069,17071],{"class":117,"line":118},[115,17070,17052],{"class":262},[115,17072,17073],{"class":132}," launch\n",[16,17075,17076],{},"Choose:",[63,17078,17079,17082,17085],{},[66,17080,17081],{},"an app name",[66,17083,17084],{},"a region close to your users or database",[66,17086,17087],{},"Dockerfile-based deploy if prompted",[16,17089,17090,17091,17094],{},"Do not trust generated defaults blindly. Review ",[20,17092,17093],{},"fly.toml"," after launch.",[16,17096,17097,17098,17100],{},"A practical ",[20,17099,17093],{}," looks like this:",[106,17102,17106],{"className":17103,"code":17104,"language":17105,"meta":111,"style":111},"language-toml shiki shiki-themes github-light github-dark","app = \"your-app-name\"\nprimary_region = \"iad\"\n\n[http_service]\n  internal_port = 8000\n  force_https = true\n  auto_stop_machines = false\n  auto_start_machines = true\n  min_machines_running = 1\n\n  [[http_service.checks]]\n    interval = \"15s\"\n    timeout = \"5s\"\n    grace_period = \"20s\"\n    method = \"GET\"\n    path = \"\u002Fhealthz\"\n\n[deploy]\n  release_command = \"python manage.py migrate --noinput --settings=project.settings.production\"\n","toml",[20,17107,17108,17113,17118,17122,17127,17132,17137,17142,17147,17152,17156,17161,17166,17171,17176,17181,17186,17190,17195],{"__ignoreMap":111},[115,17109,17110],{"class":117,"line":118},[115,17111,17112],{},"app = \"your-app-name\"\n",[115,17114,17115],{"class":117,"line":136},[115,17116,17117],{},"primary_region = \"iad\"\n",[115,17119,17120],{"class":117,"line":149},[115,17121,310],{"emptyLinePlaceholder":309},[115,17123,17124],{"class":117,"line":162},[115,17125,17126],{},"[http_service]\n",[115,17128,17129],{"class":117,"line":175},[115,17130,17131],{},"  internal_port = 8000\n",[115,17133,17134],{"class":117,"line":350},[115,17135,17136],{},"  force_https = true\n",[115,17138,17139],{"class":117,"line":365},[115,17140,17141],{},"  auto_stop_machines = false\n",[115,17143,17144],{"class":117,"line":380},[115,17145,17146],{},"  auto_start_machines = true\n",[115,17148,17149],{"class":117,"line":487},[115,17150,17151],{},"  min_machines_running = 1\n",[115,17153,17154],{"class":117,"line":2095},[115,17155,310],{"emptyLinePlaceholder":309},[115,17157,17158],{"class":117,"line":2104},[115,17159,17160],{},"  [[http_service.checks]]\n",[115,17162,17163],{"class":117,"line":2113},[115,17164,17165],{},"    interval = \"15s\"\n",[115,17167,17168],{"class":117,"line":2122},[115,17169,17170],{},"    timeout = \"5s\"\n",[115,17172,17173],{"class":117,"line":2131},[115,17174,17175],{},"    grace_period = \"20s\"\n",[115,17177,17178],{"class":117,"line":2136},[115,17179,17180],{},"    method = \"GET\"\n",[115,17182,17183],{"class":117,"line":2142},[115,17184,17185],{},"    path = \"\u002Fhealthz\"\n",[115,17187,17188],{"class":117,"line":2273},[115,17189,310],{"emptyLinePlaceholder":309},[115,17191,17192],{"class":117,"line":2282},[115,17193,17194],{},"[deploy]\n",[115,17196,17197],{"class":117,"line":2291},[115,17198,17199],{},"  release_command = \"python manage.py migrate --noinput --settings=project.settings.production\"\n",[16,17201,17202],{},"Important points:",[63,17204,17205,17211,17214],{},[66,17206,17207,17210],{},[20,17208,17209],{},"internal_port"," must match Gunicorn’s bind port.",[66,17212,17213],{},"Health checks should hit a lightweight endpoint.",[66,17215,17216,17217,17220],{},"Migrations belong in ",[20,17218,17219],{},"release_command",", not in ad hoc shell sessions.",[16,17222,17223],{},"Add a minimal health endpoint before deploying:",[106,17225,17227],{"className":2369,"code":17226,"language":1114,"meta":111,"style":111},"# project\u002Furls.py\nfrom django.http import HttpResponse\nfrom django.urls import path\n\ndef healthz(request):\n    return HttpResponse(\"ok\", content_type=\"text\u002Fplain\")\n\nurlpatterns = [\n    path(\"healthz\", healthz),\n]\n",[20,17228,17229,17234,17246,17258,17262,17272,17294,17298,17307,17318],{"__ignoreMap":111},[115,17230,17231],{"class":117,"line":118},[115,17232,17233],{"class":3861},"# project\u002Furls.py\n",[115,17235,17236,17238,17241,17243],{"class":117,"line":136},[115,17237,5621],{"class":121},[115,17239,17240],{"class":125}," django.http ",[115,17242,5613],{"class":121},[115,17244,17245],{"class":125}," HttpResponse\n",[115,17247,17248,17250,17253,17255],{"class":117,"line":149},[115,17249,5621],{"class":121},[115,17251,17252],{"class":125}," django.urls ",[115,17254,5613],{"class":121},[115,17256,17257],{"class":125}," path\n",[115,17259,17260],{"class":117,"line":162},[115,17261,310],{"emptyLinePlaceholder":309},[115,17263,17264,17266,17269],{"class":117,"line":175},[115,17265,8808],{"class":121},[115,17267,17268],{"class":262}," healthz",[115,17270,17271],{"class":125},"(request):\n",[115,17273,17274,17276,17279,17282,17284,17287,17289,17292],{"class":117,"line":350},[115,17275,3822],{"class":121},[115,17277,17278],{"class":125}," HttpResponse(",[115,17280,17281],{"class":132},"\"ok\"",[115,17283,1153],{"class":125},[115,17285,17286],{"class":5680},"content_type",[115,17288,129],{"class":121},[115,17290,17291],{"class":132},"\"text\u002Fplain\"",[115,17293,2394],{"class":125},[115,17295,17296],{"class":117,"line":365},[115,17297,310],{"emptyLinePlaceholder":309},[115,17299,17300,17303,17305],{"class":117,"line":380},[115,17301,17302],{"class":125},"urlpatterns ",[115,17304,129],{"class":121},[115,17306,3540],{"class":125},[115,17308,17309,17312,17315],{"class":117,"line":487},[115,17310,17311],{"class":125},"    path(",[115,17313,17314],{"class":132},"\"healthz\"",[115,17316,17317],{"class":125},", healthz),\n",[115,17319,17320],{"class":117,"line":2095},[115,17321,2552],{"class":125},[16,17323,17324,17325,17328],{},"If your project already has URL patterns, just add the ",[20,17326,17327],{},"path(\"healthz\", healthz)"," route.",[11,17330,17332],{"id":17331},"_5-provision-and-attach-postgres","5) Provision and attach Postgres",[16,17334,17335],{},"Create a separate Fly Postgres service:",[106,17337,17339],{"className":108,"code":17338,"language":110,"meta":111,"style":111},"fly postgres create\n",[20,17340,17341],{"__ignoreMap":111},[115,17342,17343,17345,17347],{"class":117,"line":118},[115,17344,17052],{"class":262},[115,17346,14598],{"class":132},[115,17348,17349],{"class":132}," create\n",[16,17351,17352],{},"Pick a region close to the Django app when possible. Keep database and app as separate services so compute changes and database lifecycle stay independent.",[16,17354,17355],{},"Attach Postgres to the app:",[106,17357,17359],{"className":108,"code":17358,"language":110,"meta":111,"style":111},"fly postgres attach \u003Cdb-app-name>\n",[20,17360,17361],{"__ignoreMap":111},[115,17362,17363,17365,17367,17370,17372,17375,17378],{"class":117,"line":118},[115,17364,17052],{"class":262},[115,17366,14598],{"class":132},[115,17368,17369],{"class":132}," attach",[115,17371,7691],{"class":121},[115,17373,17374],{"class":132},"db-app-nam",[115,17376,17377],{"class":125},"e",[115,17379,17380],{"class":121},">\n",[16,17382,17383,17384,17386],{},"Then verify that ",[20,17385,10873],{}," is available in the app runtime environment before deploying.",[16,17388,17389],{},"Verification checks:",[106,17391,17393],{"className":108,"code":17392,"language":110,"meta":111,"style":111},"fly secrets list\nfly ssh console\n",[20,17394,17395,17405],{"__ignoreMap":111},[115,17396,17397,17399,17402],{"class":117,"line":118},[115,17398,17052],{"class":262},[115,17400,17401],{"class":132}," secrets",[115,17403,17404],{"class":132}," list\n",[115,17406,17407,17409,17412],{"class":117,"line":136},[115,17408,17052],{"class":262},[115,17410,17411],{"class":132}," ssh",[115,17413,17414],{"class":132}," console\n",[16,17416,17417],{},"From the SSH console, confirm the variable exists:",[106,17419,17421],{"className":108,"code":17420,"language":110,"meta":111,"style":111},"printenv DATABASE_URL\n",[20,17422,17423],{"__ignoreMap":111},[115,17424,17425,17428],{"class":117,"line":118},[115,17426,17427],{"class":262},"printenv",[115,17429,17430],{"class":132}," DATABASE_URL\n",[16,17432,17433],{},"Do not paste database credentials into your repo or hardcode them in settings.",[16,17435,17436,17437,17439],{},"If your Postgres endpoint requires SSL, enforce it in Django or through connection parameters in ",[20,17438,10873],{},". For Fly private Postgres connectivity, verify the required SSL mode for your setup instead of hardcoding it blindly.",[52,17441,17443],{"id":17442},"backups-and-restore","Backups and restore",[16,17445,17446],{},"Before risky schema changes, confirm your backup and restore approach. App rollback is easier than schema rollback. Once a migration runs, redeploying an older image only works if the old code is still compatible with the new schema.",[16,17448,17449],{},"For higher-risk changes, use backward-compatible migration patterns first, then remove old code or columns in a later deploy.",[11,17451,17453],{"id":17452},"_6-configure-secrets-and-environment-variables","6) Configure secrets and environment variables",[16,17455,17456],{},"Set the required Django secrets:",[106,17458,17460],{"className":108,"code":17459,"language":110,"meta":111,"style":111},"fly secrets set \\\n  SECRET_KEY='replace-with-a-long-random-value' \\\n  DJANGO_SETTINGS_MODULE='project.settings.production'\n",[20,17461,17462,17473,17480],{"__ignoreMap":111},[115,17463,17464,17466,17468,17471],{"class":117,"line":118},[115,17465,17052],{"class":262},[115,17467,17401],{"class":132},[115,17469,17470],{"class":132}," set",[115,17472,317],{"class":202},[115,17474,17475,17478],{"class":117,"line":136},[115,17476,17477],{"class":132},"  SECRET_KEY='replace-with-a-long-random-value'",[115,17479,317],{"class":202},[115,17481,17482],{"class":117,"line":149},[115,17483,17484],{"class":132},"  DJANGO_SETTINGS_MODULE='project.settings.production'\n",[16,17486,17487],{},"You may also need email, Sentry, payment, or API secrets:",[106,17489,17491],{"className":108,"code":17490,"language":110,"meta":111,"style":111},"fly secrets set SENTRY_DSN='...' EMAIL_HOST_PASSWORD='...'\n",[20,17492,17493],{"__ignoreMap":111},[115,17494,17495,17497,17499,17501,17504],{"class":117,"line":118},[115,17496,17052],{"class":262},[115,17498,17401],{"class":132},[115,17500,17470],{"class":132},[115,17502,17503],{"class":132}," SENTRY_DSN='...'",[115,17505,17506],{"class":132}," EMAIL_HOST_PASSWORD='...'\n",[16,17508,17509,17510,17512],{},"Keep local ",[20,17511,191],{}," files out of version control. Use local environment files only for development, and Fly secrets for production.",[16,17514,3515],{},[106,17516,17518],{"className":108,"code":17517,"language":110,"meta":111,"style":111},"fly secrets list\n",[20,17519,17520],{"__ignoreMap":111},[115,17521,17522,17524,17526],{"class":117,"line":118},[115,17523,17052],{"class":262},[115,17525,17401],{"class":132},[115,17527,17404],{"class":132},[11,17529,17531],{"id":17530},"_7-deploy-the-app","7) Deploy the app",[16,17533,17534],{},"Run the first deploy:",[106,17536,17538],{"className":108,"code":17537,"language":110,"meta":111,"style":111},"fly deploy\n",[20,17539,17540],{"__ignoreMap":111},[115,17541,17542,17544],{"class":117,"line":118},[115,17543,17052],{"class":262},[115,17545,14215],{"class":132},[16,17547,17548],{},"Watch for three things in the output:",[1173,17550,17551,17554,17557],{},[66,17552,17553],{},"image build success",[66,17555,17556],{},"release command success",[66,17558,17559],{},"machine health check success",[16,17561,17562],{},"If migrations fail in release phase, the deploy should not promote successfully. That is what you want. Fix the migration issue before retrying.",[16,17564,17565],{},"Because the release command runs before the new version starts serving traffic, this reduces one common deployment mistake. It does not remove rollback risk if the schema change is not backward compatible.",[11,17567,17569],{"id":17568},"_8-expose-the-app-securely","8) Expose the app securely",[16,17571,17572],{},"Fly terminates TLS at the proxy and forwards the request to your app. Django must trust the forwarded protocol so it knows the original request was HTTPS:",[106,17574,17576],{"className":2369,"code":17575,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nUSE_X_FORWARDED_HOST = True\n",[20,17577,17578,17594],{"__ignoreMap":111},[115,17579,17580,17582,17584,17586,17588,17590,17592],{"class":117,"line":118},[115,17581,2377],{"class":202},[115,17583,2380],{"class":121},[115,17585,2383],{"class":125},[115,17587,2386],{"class":132},[115,17589,1153],{"class":125},[115,17591,2391],{"class":132},[115,17593,2394],{"class":125},[115,17595,17596,17598,17600],{"class":117,"line":136},[115,17597,12021],{"class":202},[115,17599,2380],{"class":121},[115,17601,2412],{"class":202},[16,17603,17604],{},"Without correct proxy settings, you may see redirect loops, wrong absolute URLs, host validation issues, or CSRF failures.",[16,17606,17607],{},"If using a custom domain, add it in Fly and then update DNS as instructed by Fly:",[106,17609,17611],{"className":108,"code":17610,"language":110,"meta":111,"style":111},"fly certs add example.com\nfly certs add www.example.com\n",[20,17612,17613,17625],{"__ignoreMap":111},[115,17614,17615,17617,17620,17623],{"class":117,"line":118},[115,17616,17052],{"class":262},[115,17618,17619],{"class":132}," certs",[115,17621,17622],{"class":132}," add",[115,17624,6454],{"class":132},[115,17626,17627,17629,17631,17633],{"class":117,"line":136},[115,17628,17052],{"class":262},[115,17630,17619],{"class":132},[115,17632,17622],{"class":132},[115,17634,6696],{"class":132},[16,17636,17637],{},"After DNS propagates, verify certificate status:",[106,17639,17641],{"className":108,"code":17640,"language":110,"meta":111,"style":111},"fly certs show example.com\n",[20,17642,17643],{"__ignoreMap":111},[115,17644,17645,17647,17649,17651],{"class":117,"line":118},[115,17646,17052],{"class":262},[115,17648,17619],{"class":132},[115,17650,12372],{"class":132},[115,17652,6454],{"class":132},[16,17654,17655],{},"Then confirm:",[63,17657,17658,17663,17668],{},[66,17659,17660,17661],{},"the domain is present in ",[20,17662,2719],{},[66,17664,17665,17666],{},"the HTTPS origin is present in ",[20,17667,2725],{},[66,17669,17670],{},"redirects and canonical URLs behave as expected",[11,17672,17674],{"id":17673},"_9-verify-the-deployment","9) Verify the deployment",[16,17676,17677],{},"Start with Fly status and logs:",[106,17679,17681],{"className":108,"code":17680,"language":110,"meta":111,"style":111},"fly status\nfly logs\n",[20,17682,17683,17689],{"__ignoreMap":111},[115,17684,17685,17687],{"class":117,"line":118},[115,17686,17052],{"class":262},[115,17688,2017],{"class":132},[115,17690,17691,17693],{"class":117,"line":136},[115,17692,17052],{"class":262},[115,17694,17695],{"class":132}," logs\n",[16,17697,17698],{},"Then verify the live app:",[106,17700,17702],{"className":108,"code":17701,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fyour-app-name.fly.dev\u002F\ncurl -I https:\u002F\u002Fyour-app-name.fly.dev\u002Fhealthz\ncurl -I https:\u002F\u002Fyour-app-name.fly.dev\u002Fstatic\u002Fpath-to-known-file.css\n",[20,17703,17704,17713,17722],{"__ignoreMap":111},[115,17705,17706,17708,17710],{"class":117,"line":118},[115,17707,2764],{"class":262},[115,17709,2767],{"class":202},[115,17711,17712],{"class":132}," https:\u002F\u002Fyour-app-name.fly.dev\u002F\n",[115,17714,17715,17717,17719],{"class":117,"line":136},[115,17716,2764],{"class":262},[115,17718,2767],{"class":202},[115,17720,17721],{"class":132}," https:\u002F\u002Fyour-app-name.fly.dev\u002Fhealthz\n",[115,17723,17724,17726,17728],{"class":117,"line":149},[115,17725,2764],{"class":262},[115,17727,2767],{"class":202},[115,17729,17730],{"class":132}," https:\u002F\u002Fyour-app-name.fly.dev\u002Fstatic\u002Fpath-to-known-file.css\n",[16,17732,17733],{},"Test these manually:",[63,17735,17736,17742,17745,17748,17751,17754],{},[66,17737,17738,17739],{},"home page returns ",[20,17740,17741],{},"200",[66,17743,17744],{},"admin login page loads",[66,17746,17747],{},"static assets are served",[66,17749,17750],{},"one database-backed page reads and writes correctly",[66,17752,17753],{},"HTTPS redirect behavior is correct",[66,17755,17756],{},"no debug information is exposed",[16,17758,17759],{},"You can also run:",[106,17761,17762],{"className":108,"code":16766,"language":110,"meta":111,"style":111},[20,17763,17764],{"__ignoreMap":111},[115,17765,17766,17768,17770,17772,17774],{"class":117,"line":118},[115,17767,1114],{"class":262},[115,17769,1117],{"class":132},[115,17771,1814],{"class":132},[115,17773,16779],{"class":202},[115,17775,16782],{"class":202},[16,17777,17778],{},"That command runs locally, but it is still useful as a production settings audit.",[11,17780,1321],{"id":1320},[16,17782,17783],{},"This setup works because each production concern is handled in the right layer:",[63,17785,17786,17789,17792,17795,17798,17801],{},[66,17787,17788],{},"Fly.io runs the containerized Django app.",[66,17790,17791],{},"Gunicorn provides a stable application server.",[66,17793,17794],{},"Fly Postgres provides PostgreSQL separately from app compute.",[66,17796,17797],{},"Fly secrets keep sensitive configuration out of Git.",[66,17799,17800],{},"Release-phase migrations reduce the chance of forgetting schema updates.",[66,17802,17803],{},"WhiteNoise handles static files without introducing another service for simpler deployments.",[16,17805,17806],{},"WhiteNoise is a good default when your static assets are modest and you want fewer moving parts. If you expect large static asset volume or need a CDN-heavy setup, move static files to object storage and a CDN. For user-uploaded media, use external object storage instead of the app filesystem.",[52,17808,17810],{"id":17809},"when-manual-flyio-deployment-becomes-repetitive","When manual Fly.io deployment becomes repetitive",[16,17812,17813,17814,17816],{},"Once you repeat this process across environments, the first parts to automate are secret loading, pre-deploy checks, migration gating, and post-deploy smoke tests. A reusable Dockerfile, hardened ",[20,17815,17093],{},", and deploy script usually remove the most common human errors without changing the underlying architecture.",[11,17818,1337],{"id":1336},[63,17820,17821,17833,17841,17850,17859,17874,17882,17888,17894,17900],{},[66,17822,17823,17826,17827,17829,17830,17832],{},[1226,17824,17825],{},"Static files 404",": usually means ",[20,17828,13689],{}," did not run, ",[20,17831,11918],{}," is wrong, or WhiteNoise is missing from middleware.",[66,17834,17835,17840],{},[1226,17836,17837,17838],{},"Build fails during ",[20,17839,13689],{},": your settings likely require runtime-only environment variables or import database-dependent code too early.",[66,17842,17843,17846,17847,17849],{},[1226,17844,17845],{},"App crashes on boot",": often caused by wrong WSGI module, missing dependency, or ",[20,17848,17209],{}," mismatch.",[66,17851,17852,17855,17856,17858],{},[1226,17853,17854],{},"Database connection errors",": check that Postgres is attached and ",[20,17857,10873],{}," exists in the runtime environment.",[66,17860,17861,17864,17865,17867,17868,17870,17871,17873],{},[1226,17862,17863],{},"CSRF failures behind HTTPS",": verify ",[20,17866,2725],{}," includes ",[20,17869,16092],{}," origins and ",[20,17872,2377],{}," is set.",[66,17875,17876,17864,17879,17881],{},[1226,17877,17878],{},"Wrong absolute URLs or host handling",[20,17880,13891],{}," and confirm your allowed hosts match the real public domain.",[66,17883,17884,17887],{},[1226,17885,17886],{},"Rollback limits",": code-only rollback is straightforward; schema rollback is not. Prefer backward-compatible migrations when possible.",[66,17889,17890,17893],{},[1226,17891,17892],{},"Media uploads",": do not rely on the app filesystem for persistent user media unless you have explicitly designed around Fly volumes and their constraints.",[66,17895,17896,17899],{},[1226,17897,17898],{},"Worker count",": tune Gunicorn workers to available memory. Do not blindly increase workers on small app instances.",[66,17901,17902,17905],{},[1226,17903,17904],{},"Long requests",": if you have slow views or large background jobs, move that work out of web requests instead of only raising timeouts.",[11,17907,1386],{"id":1385},[16,17909,17910,17911,211],{},"If you need the broader hardening checklist first, see the ",[1395,17912,3000],{"href":2999},[16,17914,17915,17916,211],{},"For non-platform-specific container patterns, compare this with ",[1395,17917,17918],{"href":1403},"Deploy Django with Docker and PostgreSQL",[16,17920,17921,17922,211],{},"If you want a more traditional VM setup, see ",[1395,17923,2986],{"href":2985},[16,17925,17926,17927,211],{},"Before changing release flow or migrations in production, review ",[1395,17928,17929],{"href":1415},"How to Roll Back a Django Deployment Safely",[11,17931,1420],{"id":1419},[52,17933,17935],{"id":17934},"can-i-deploy-django-on-flyio-without-docker","Can I deploy Django on Fly.io without Docker?",[16,17937,17938],{},"Fly.io is typically used with containerized deployments. In practice, for Django, Docker is the standard path because it gives you a repeatable build, predictable dependencies, and a clean release process.",[52,17940,17942],{"id":17941},"should-migrations-run-during-every-deploy","Should migrations run during every deploy?",[16,17944,17945],{},"They should run in a controlled release phase when the new release requires schema changes. That is safer than opening a shell and running migrations manually after traffic has already started hitting the new version.",[52,17947,17949],{"id":17948},"how-do-i-serve-static-files-on-flyio","How do I serve static files on Fly.io?",[16,17951,17952,17953,17955],{},"For many Django apps, WhiteNoise is the simplest option. Collect static files during build, store them in ",[20,17954,11918],{},", and serve them from the app container. For larger deployments, move static assets to object storage and a CDN.",[52,17957,17959],{"id":17958},"can-i-scale-django-and-postgres-separately-on-flyio","Can I scale Django and Postgres separately on Fly.io?",[16,17961,17962],{},"Yes. Your Django app and Fly Postgres service are separate. That separation makes production operation easier because app compute and database sizing are managed independently.",[52,17964,17966],{"id":17965},"what-is-the-safest-rollback-approach-after-a-failed-deploy","What is the safest rollback approach after a failed deploy?",[16,17968,17969],{},"If the failure is code or config only, redeploy the last known good image or release and confirm health checks. If migrations already ran, first confirm the older code is compatible with the current schema. If not, a forward fix is usually safer than a rushed database restore.",[1485,17971,17972],{},"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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":17974},[17975,17976,17977,17978,17979,17980,17981,17982,17985,17986,17987,17988,17989,17992,17993,17994],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":16290,"depth":136,"text":16291},{"id":16799,"depth":136,"text":16800},{"id":16825,"depth":136,"text":16826},{"id":17038,"depth":136,"text":17039},{"id":17331,"depth":136,"text":17332,"children":17983},[17984],{"id":17442,"depth":149,"text":17443},{"id":17452,"depth":136,"text":17453},{"id":17530,"depth":136,"text":17531},{"id":17568,"depth":136,"text":17569},{"id":17673,"depth":136,"text":17674},{"id":1320,"depth":136,"text":1321,"children":17990},[17991],{"id":17809,"depth":149,"text":17810},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":17995},[17996,17997,17998,17999,18000],{"id":17934,"depth":149,"text":17935},{"id":17941,"depth":149,"text":17942},{"id":17948,"depth":149,"text":17949},{"id":17958,"depth":149,"text":17959},{"id":17965,"depth":149,"text":17966},"A production-safe Fly.io Django deployment needs more than fly launch and fly deploy.",{},"\u002Fdeploy-django-on-fly-io","13",[2992,3096,11637],{"title":16252,"description":18001},[1557,18008,1558],"fly-io","deploy-django-on-fly-io",[1557,18008,1558],"kqSm1mHjQv_rgxEUlHo4JsS-Yanw_rSe6MJL0fSStRc",{"id":18013,"title":18014,"body":18015,"category":3088,"description":18021,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":20254,"navigation":309,"path":20255,"priority":20256,"related":20257,"role":1553,"section":3098,"seo":20259,"stack":20260,"stem":20262,"tags":20263,"type":1561,"__hash__":20264},"articles\u002Fdeploy-django-on-google-cloud-run.md","Deploy Django on Google Cloud Run",{"type":8,"value":18016,"toc":20221},[18017,18019,18022,18029,18035,18054,18057,18059,18066,18101,18104,18106,18110,18113,18147,18154,18157,18174,18178,18181,18448,18451,18572,18575,18578,18680,18682,18706,18716,18720,18723,18814,18820,18856,18862,18865,18945,18951,18954,18958,18961,19027,19030,19065,19068,19085,19088,19116,19119,19142,19149,19153,19156,19159,19210,19216,19219,19231,19233,19247,19251,19257,19359,19362,19483,19486,19540,19546,19548,19566,19572,19576,19581,19588,19591,19687,19690,19715,19718,19752,19755,19766,19769,19773,19776,19817,19819,19842,19845,19875,19878,19892,19902,19906,19909,19912,19927,19930,19934,19937,19960,19963,20002,20005,20023,20026,20037,20039,20042,20045,20047,20051,20054,20072,20076,20082,20086,20089,20111,20115,20117,20129,20133,20136,20140,20146,20148,20151,20153,20177,20179,20183,20186,20190,20193,20197,20200,20204,20207,20211,20218],[11,18018,14],{"id":13},[16,18020,18021],{},"If you want to deploy Django on Google Cloud Run, the basic path is straightforward: build a container and run it. The production-safe part is where most deployments fail.",[16,18023,18024,18025,18028],{},"A real ",[1226,18026,18027],{},"Django Cloud Run deployment"," needs more than a working container. You need production settings, secret management, database connectivity, static file handling, health checks, a migration workflow, and a rollback path. Cloud Run is also stateless, so anything that depends on local persistent storage will break.",[16,18030,18031,18032,1642],{},"This guide shows a practical way to ",[1226,18033,18034],{},"deploy Django to Google Cloud Run",[63,18036,18037,18040,18043,18046,18049,18051],{},[66,18038,18039],{},"Cloud Run for app hosting",[66,18041,18042],{},"Artifact Registry for images",[66,18044,18045],{},"Secret Manager for secrets",[66,18047,18048],{},"Cloud SQL for PostgreSQL",[66,18050,14067],{},[66,18052,18053],{},"WhiteNoise for static files inside the container",[16,18055,18056],{},"This guide does not cover Terraform or a full CI\u002FCD pipeline. It focuses on a manual deployment path you can verify first, then automate later.",[11,18058,30],{"id":29},[16,18060,18061,18062,18065],{},"To ",[1226,18063,18064],{},"deploy Django on Google Cloud Run",", use this path:",[1173,18067,18068,18071,18074,18080,18083,18086,18089,18092,18095,18098],{},[66,18069,18070],{},"Prepare Django production settings",[66,18072,18073],{},"Add a health endpoint",[66,18075,18076,18077],{},"Build a Docker image that runs Gunicorn on ",[20,18078,18079],{},"$PORT",[66,18081,18082],{},"Push the image to Artifact Registry",[66,18084,18085],{},"Store secrets in Secret Manager",[66,18087,18088],{},"Connect Cloud Run to Cloud SQL",[66,18090,18091],{},"Deploy the service with environment variables and secrets",[66,18093,18094],{},"Run migrations as a separate step",[66,18096,18097],{},"Verify health, logs, static files, and database access",[66,18099,18100],{},"Keep the previous revision available for rollback",[16,18102,18103],{},"Cloud Run fits well for stateless Django apps, APIs, admin panels, and moderate web workloads. It is a weaker fit if you require persistent local disk, long-running in-request jobs, or filesystem-based media storage.",[11,18105,43],{"id":42},[11,18107,18109],{"id":18108},"_1-choose-the-production-architecture-for-django-on-cloud-run","1) Choose the production architecture for Django on Cloud Run",[16,18111,18112],{},"Recommended stack:",[63,18114,18115,18121,18127,18136,18141],{},[66,18116,18117,18120],{},[1226,18118,18119],{},"Cloud Run",": runs the Django container",[66,18122,18123,18126],{},[1226,18124,18125],{},"Artifact Registry",": stores container images",[66,18128,18129,18132,18133,18135],{},[1226,18130,18131],{},"Secret Manager",": stores ",[20,18134,2713],{},", database credentials, and similar secrets",[66,18137,18138,18140],{},[1226,18139,18048],{},": production database",[66,18142,18143,18146],{},[1226,18144,18145],{},"Cloud Logging",": startup, request, and application logs",[16,18148,18149,18150,18153],{},"Cloud Run starts your container, sends traffic to the port in the ",[20,18151,18152],{},"PORT"," environment variable, and expects the app to be stateless. The container filesystem is ephemeral. Do not store uploads or generated files there permanently.",[16,18155,18156],{},"Constraints to design around:",[63,18158,18159,18162,18165,18168,18171],{},[66,18160,18161],{},"no persistent local media storage",[66,18163,18164],{},"cold starts are possible",[66,18166,18167],{},"requests have a timeout limit",[66,18169,18170],{},"scaling is automatic and can create multiple app instances",[66,18172,18173],{},"startup should be fast and deterministic",[11,18175,18177],{"id":18176},"_2-prepare-django-settings-for-cloud-run-production","2) Prepare Django settings for Cloud Run production",[16,18179,18180],{},"Use environment-based settings. A minimal production pattern looks like this:",[106,18182,18184],{"className":2369,"code":18183,"language":1114,"meta":111,"style":111},"# config\u002Fsettings\u002Fproduction.py\nimport os\nfrom pathlib import Path\nimport dj_database_url\n\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\nDEBUG = False\n\nALLOWED_HOSTS = [h.strip() for h in os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\") if h.strip()]\nCSRF_TRUSTED_ORIGINS = [o.strip() for o in os.environ.get(\"CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if o.strip()]\n\nSECRET_KEY = os.environ[\"SECRET_KEY\"]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\n\nDATABASES = {\n    \"default\": dj_database_url.parse(os.environ[\"DATABASE_URL\"], conn_max_age=600)\n}\n",[20,18185,18186,18191,18197,18207,18213,18217,18229,18233,18241,18245,18287,18323,18327,18339,18343,18359,18367,18375,18383,18387,18395,18403,18411,18415,18423,18444],{"__ignoreMap":111},[115,18187,18188],{"class":117,"line":118},[115,18189,18190],{"class":3861},"# config\u002Fsettings\u002Fproduction.py\n",[115,18192,18193,18195],{"class":117,"line":136},[115,18194,5613],{"class":121},[115,18196,5616],{"class":125},[115,18198,18199,18201,18203,18205],{"class":117,"line":149},[115,18200,5621],{"class":121},[115,18202,16353],{"class":125},[115,18204,5613],{"class":121},[115,18206,16358],{"class":125},[115,18208,18209,18211],{"class":117,"line":162},[115,18210,5613],{"class":121},[115,18212,16365],{"class":125},[115,18214,18215],{"class":117,"line":175},[115,18216,310],{"emptyLinePlaceholder":309},[115,18218,18219,18221,18223,18225,18227],{"class":117,"line":350},[115,18220,16374],{"class":202},[115,18222,2380],{"class":121},[115,18224,16379],{"class":125},[115,18226,16382],{"class":202},[115,18228,16385],{"class":125},[115,18230,18231],{"class":117,"line":365},[115,18232,310],{"emptyLinePlaceholder":309},[115,18234,18235,18237,18239],{"class":117,"line":380},[115,18236,7350],{"class":202},[115,18238,2380],{"class":121},[115,18240,7355],{"class":202},[115,18242,18243],{"class":117,"line":487},[115,18244,310],{"emptyLinePlaceholder":309},[115,18246,18247,18249,18251,18254,18257,18260,18263,18265,18268,18270,18273,18276,18279,18282,18284],{"class":117,"line":2095},[115,18248,2719],{"class":202},[115,18250,2380],{"class":121},[115,18252,18253],{"class":125}," [h.strip() ",[115,18255,18256],{"class":121},"for",[115,18258,18259],{"class":125}," h ",[115,18261,18262],{"class":121},"in",[115,18264,8884],{"class":125},[115,18266,18267],{"class":132},"\"ALLOWED_HOSTS\"",[115,18269,1153],{"class":125},[115,18271,18272],{"class":132},"\"\"",[115,18274,18275],{"class":125},").split(",[115,18277,18278],{"class":132},"\",\"",[115,18280,18281],{"class":125},") ",[115,18283,10833],{"class":121},[115,18285,18286],{"class":125}," h.strip()]\n",[115,18288,18289,18291,18293,18296,18298,18301,18303,18305,18308,18310,18312,18314,18316,18318,18320],{"class":117,"line":2104},[115,18290,2725],{"class":202},[115,18292,2380],{"class":121},[115,18294,18295],{"class":125}," [o.strip() ",[115,18297,18256],{"class":121},[115,18299,18300],{"class":125}," o ",[115,18302,18262],{"class":121},[115,18304,8884],{"class":125},[115,18306,18307],{"class":132},"\"CSRF_TRUSTED_ORIGINS\"",[115,18309,1153],{"class":125},[115,18311,18272],{"class":132},[115,18313,18275],{"class":125},[115,18315,18278],{"class":132},[115,18317,18281],{"class":125},[115,18319,10833],{"class":121},[115,18321,18322],{"class":125}," o.strip()]\n",[115,18324,18325],{"class":117,"line":2113},[115,18326,310],{"emptyLinePlaceholder":309},[115,18328,18329,18331,18333,18335,18337],{"class":117,"line":2122},[115,18330,2713],{"class":202},[115,18332,2380],{"class":121},[115,18334,8861],{"class":125},[115,18336,16412],{"class":132},[115,18338,2552],{"class":125},[115,18340,18341],{"class":117,"line":2131},[115,18342,310],{"emptyLinePlaceholder":309},[115,18344,18345,18347,18349,18351,18353,18355,18357],{"class":117,"line":2136},[115,18346,2377],{"class":202},[115,18348,2380],{"class":121},[115,18350,2383],{"class":125},[115,18352,2386],{"class":132},[115,18354,1153],{"class":125},[115,18356,2391],{"class":132},[115,18358,2394],{"class":125},[115,18360,18361,18363,18365],{"class":117,"line":2142},[115,18362,2407],{"class":202},[115,18364,2380],{"class":121},[115,18366,2412],{"class":202},[115,18368,18369,18371,18373],{"class":117,"line":2273},[115,18370,2417],{"class":202},[115,18372,2380],{"class":121},[115,18374,2412],{"class":202},[115,18376,18377,18379,18381],{"class":117,"line":2282},[115,18378,2426],{"class":202},[115,18380,2380],{"class":121},[115,18382,2412],{"class":202},[115,18384,18385],{"class":117,"line":2291},[115,18386,310],{"emptyLinePlaceholder":309},[115,18388,18389,18391,18393],{"class":117,"line":2299},[115,18390,7440],{"class":202},[115,18392,2380],{"class":121},[115,18394,11991],{"class":202},[115,18396,18397,18399,18401],{"class":117,"line":2307},[115,18398,7464],{"class":202},[115,18400,2380],{"class":121},[115,18402,2412],{"class":202},[115,18404,18405,18407,18409],{"class":117,"line":2315},[115,18406,12004],{"class":202},[115,18408,2380],{"class":121},[115,18410,2412],{"class":202},[115,18412,18413],{"class":117,"line":2320},[115,18414,310],{"emptyLinePlaceholder":309},[115,18416,18417,18419,18421],{"class":117,"line":7083},[115,18418,10632],{"class":202},[115,18420,2380],{"class":121},[115,18422,2166],{"class":125},[115,18424,18425,18427,18430,18432,18435,18438,18440,18442],{"class":117,"line":7090},[115,18426,10664],{"class":132},[115,18428,18429],{"class":125},": dj_database_url.parse(os.environ[",[115,18431,16727],{"class":132},[115,18433,18434],{"class":125},"], ",[115,18436,18437],{"class":5680},"conn_max_age",[115,18439,129],{"class":121},[115,18441,16740],{"class":202},[115,18443,2394],{"class":125},[115,18445,18446],{"class":117,"line":7097},[115,18447,2323],{"class":125},[16,18449,18450],{},"For static files with WhiteNoise:",[106,18452,18454],{"className":2369,"code":18453,"language":1114,"meta":111,"style":111},"INSTALLED_APPS = [\n    \"django.contrib.staticfiles\",\n    # ...\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # ...\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nSTORAGES = {\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    },\n}\n",[20,18455,18456,18465,18472,18476,18480,18484,18492,18498,18504,18508,18512,18516,18524,18536,18540,18548,18554,18564,18568],{"__ignoreMap":111},[115,18457,18458,18461,18463],{"class":117,"line":118},[115,18459,18460],{"class":202},"INSTALLED_APPS",[115,18462,2380],{"class":121},[115,18464,3540],{"class":125},[115,18466,18467,18470],{"class":117,"line":136},[115,18468,18469],{"class":132},"    \"django.contrib.staticfiles\"",[115,18471,3354],{"class":125},[115,18473,18474],{"class":117,"line":149},[115,18475,16643],{"class":3861},[115,18477,18478],{"class":117,"line":162},[115,18479,2552],{"class":125},[115,18481,18482],{"class":117,"line":175},[115,18483,310],{"emptyLinePlaceholder":309},[115,18485,18486,18488,18490],{"class":117,"line":350},[115,18487,16617],{"class":202},[115,18489,2380],{"class":121},[115,18491,3540],{"class":125},[115,18493,18494,18496],{"class":117,"line":365},[115,18495,16627],{"class":132},[115,18497,3354],{"class":125},[115,18499,18500,18502],{"class":117,"line":380},[115,18501,16635],{"class":132},[115,18503,3354],{"class":125},[115,18505,18506],{"class":117,"line":487},[115,18507,16643],{"class":3861},[115,18509,18510],{"class":117,"line":2095},[115,18511,2552],{"class":125},[115,18513,18514],{"class":117,"line":2104},[115,18515,310],{"emptyLinePlaceholder":309},[115,18517,18518,18520,18522],{"class":117,"line":2113},[115,18519,11908],{"class":202},[115,18521,2380],{"class":121},[115,18523,11913],{"class":132},[115,18525,18526,18528,18530,18532,18534],{"class":117,"line":2122},[115,18527,11918],{"class":202},[115,18529,2380],{"class":121},[115,18531,11923],{"class":202},[115,18533,11926],{"class":121},[115,18535,11929],{"class":132},[115,18537,18538],{"class":117,"line":2131},[115,18539,310],{"emptyLinePlaceholder":309},[115,18541,18542,18544,18546],{"class":117,"line":2136},[115,18543,16659],{"class":202},[115,18545,2380],{"class":121},[115,18547,2166],{"class":125},[115,18549,18550,18552],{"class":117,"line":2142},[115,18551,16669],{"class":132},[115,18553,3374],{"class":125},[115,18555,18556,18558,18560,18562],{"class":117,"line":2273},[115,18557,16677],{"class":132},[115,18559,2513],{"class":125},[115,18561,16682],{"class":132},[115,18563,3354],{"class":125},[115,18565,18566],{"class":117,"line":2282},[115,18567,3403],{"class":125},[115,18569,18570],{"class":117,"line":2291},[115,18571,2323],{"class":125},[16,18573,18574],{},"Media files should not use the Cloud Run filesystem. Use object storage such as Google Cloud Storage.",[16,18576,18577],{},"Add a lightweight health endpoint:",[106,18579,18581],{"className":2369,"code":18580,"language":1114,"meta":111,"style":111},"# urls.py\nfrom django.http import JsonResponse\nfrom django.urls import path, include\n\ndef health(request):\n    return JsonResponse({\"status\": \"ok\"})\n\nurlpatterns = [\n    path(\"health\u002F\", health),\n    path(\"\", include(\"your_app.urls\")),\n]\n",[20,18582,18583,18588,18599,18610,18614,18623,18640,18644,18652,18662,18676],{"__ignoreMap":111},[115,18584,18585],{"class":117,"line":118},[115,18586,18587],{"class":3861},"# urls.py\n",[115,18589,18590,18592,18594,18596],{"class":117,"line":136},[115,18591,5621],{"class":121},[115,18593,17240],{"class":125},[115,18595,5613],{"class":121},[115,18597,18598],{"class":125}," JsonResponse\n",[115,18600,18601,18603,18605,18607],{"class":117,"line":149},[115,18602,5621],{"class":121},[115,18604,17252],{"class":125},[115,18606,5613],{"class":121},[115,18608,18609],{"class":125}," path, include\n",[115,18611,18612],{"class":117,"line":162},[115,18613,310],{"emptyLinePlaceholder":309},[115,18615,18616,18618,18621],{"class":117,"line":175},[115,18617,8808],{"class":121},[115,18619,18620],{"class":262}," health",[115,18622,17271],{"class":125},[115,18624,18625,18627,18630,18633,18635,18637],{"class":117,"line":350},[115,18626,3822],{"class":121},[115,18628,18629],{"class":125}," JsonResponse({",[115,18631,18632],{"class":132},"\"status\"",[115,18634,2513],{"class":125},[115,18636,17281],{"class":132},[115,18638,18639],{"class":125},"})\n",[115,18641,18642],{"class":117,"line":365},[115,18643,310],{"emptyLinePlaceholder":309},[115,18645,18646,18648,18650],{"class":117,"line":380},[115,18647,17302],{"class":125},[115,18649,129],{"class":121},[115,18651,3540],{"class":125},[115,18653,18654,18656,18659],{"class":117,"line":487},[115,18655,17311],{"class":125},[115,18657,18658],{"class":132},"\"health\u002F\"",[115,18660,18661],{"class":125},", health),\n",[115,18663,18664,18666,18668,18671,18674],{"class":117,"line":2095},[115,18665,17311],{"class":125},[115,18667,18272],{"class":132},[115,18669,18670],{"class":125},", include(",[115,18672,18673],{"class":132},"\"your_app.urls\"",[115,18675,10770],{"class":125},[115,18677,18678],{"class":117,"line":2104},[115,18679,2552],{"class":125},[16,18681,3515],{},[63,18683,18684,18688,18693,18701],{},[66,18685,18686],{},[20,18687,2707],{},[66,18689,18690,18692],{},[20,18691,2719],{}," includes the actual Cloud Run service hostname and any custom domain",[66,18694,18695,18697,18698,18700],{},[20,18696,2725],{}," includes full ",[20,18699,16092],{}," origins",[66,18702,18703,18705],{},[20,18704,15970],{}," passes or shows only intentional warnings",[16,18707,18708,18709,3146,18712,18715],{},"Note on HSTS: ",[20,18710,18711],{},"includeSubDomains",[20,18713,18714],{},"preload"," are strict settings. Use them only if your domain setup is ready for that policy across all relevant subdomains.",[11,18717,18719],{"id":18718},"_3-create-the-docker-image-for-django","3) Create the Docker image for Django",[16,18721,18722],{},"Use a production Dockerfile that installs dependencies, collects static files, and runs Gunicorn:",[106,18724,18726],{"className":16832,"code":18725,"language":16834,"meta":111,"style":111},"FROM python:3.12-slim\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\nENV DJANGO_SETTINGS_MODULE=config.settings.production\n\nWORKDIR \u002Fapp\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nRUN python manage.py collectstatic --noinput\n\nCMD exec gunicorn config.wsgi:application --bind 0.0.0.0:${PORT:-8080} --workers 2 --threads 4 --timeout 120\n",[20,18727,18728,18734,18738,18744,18750,18757,18761,18767,18771,18777,18783,18787,18793,18797,18803,18807],{"__ignoreMap":111},[115,18729,18730,18732],{"class":117,"line":118},[115,18731,11089],{"class":121},[115,18733,16843],{"class":125},[115,18735,18736],{"class":117,"line":136},[115,18737,310],{"emptyLinePlaceholder":309},[115,18739,18740,18742],{"class":117,"line":149},[115,18741,16852],{"class":121},[115,18743,16855],{"class":125},[115,18745,18746,18748],{"class":117,"line":162},[115,18747,16852],{"class":121},[115,18749,16862],{"class":125},[115,18751,18752,18754],{"class":117,"line":175},[115,18753,16852],{"class":121},[115,18755,18756],{"class":125}," DJANGO_SETTINGS_MODULE=config.settings.production\n",[115,18758,18759],{"class":117,"line":350},[115,18760,310],{"emptyLinePlaceholder":309},[115,18762,18763,18765],{"class":117,"line":365},[115,18764,16885],{"class":121},[115,18766,16888],{"class":125},[115,18768,18769],{"class":117,"line":380},[115,18770,310],{"emptyLinePlaceholder":309},[115,18772,18773,18775],{"class":117,"line":487},[115,18774,16897],{"class":121},[115,18776,16900],{"class":125},[115,18778,18779,18781],{"class":117,"line":2095},[115,18780,16905],{"class":121},[115,18782,16908],{"class":125},[115,18784,18785],{"class":117,"line":2104},[115,18786,310],{"emptyLinePlaceholder":309},[115,18788,18789,18791],{"class":117,"line":2113},[115,18790,16897],{"class":121},[115,18792,16919],{"class":125},[115,18794,18795],{"class":117,"line":2122},[115,18796,310],{"emptyLinePlaceholder":309},[115,18798,18799,18801],{"class":117,"line":2131},[115,18800,16905],{"class":121},[115,18802,16930],{"class":125},[115,18804,18805],{"class":117,"line":2136},[115,18806,310],{"emptyLinePlaceholder":309},[115,18808,18809,18811],{"class":117,"line":2142},[115,18810,16939],{"class":121},[115,18812,18813],{"class":125}," exec gunicorn config.wsgi:application --bind 0.0.0.0:${PORT:-8080} --workers 2 --threads 4 --timeout 120\n",[16,18815,18816,18817,241],{},"Add a ",[20,18818,18819],{},".dockerignore",[106,18821,18825],{"className":18822,"code":18823,"language":18824,"meta":111,"style":111},"language-gitignore shiki shiki-themes github-light github-dark",".git\n__pycache__\u002F\n*.pyc\n.env\nvenv\u002F\nnode_modules\u002F\n","gitignore",[20,18826,18827,18832,18837,18842,18846,18851],{"__ignoreMap":111},[115,18828,18829],{"class":117,"line":118},[115,18830,18831],{},".git\n",[115,18833,18834],{"class":117,"line":136},[115,18835,18836],{},"__pycache__\u002F\n",[115,18838,18839],{"class":117,"line":149},[115,18840,18841],{},"*.pyc\n",[115,18843,18844],{"class":117,"line":162},[115,18845,2526],{},[115,18847,18848],{"class":117,"line":175},[115,18849,18850],{},"venv\u002F\n",[115,18852,18853],{"class":117,"line":350},[115,18854,18855],{},"node_modules\u002F\n",[16,18857,18858,18859,18861],{},"Key requirement: Gunicorn must bind to ",[20,18860,18079],{},". Cloud Run injects that port at runtime.",[16,18863,18864],{},"Verify locally before pushing:",[106,18866,18868],{"className":108,"code":18867,"language":110,"meta":111,"style":111},"docker build -t django-cloudrun-test .\ndocker run --rm -p 8080:8080 \\\n  -e PORT=8080 \\\n  -e SECRET_KEY=test-secret \\\n  -e ALLOWED_HOSTS=localhost,127.0.0.1 \\\n  -e DATABASE_URL=postgres:\u002F\u002FUSER:PASSWORD@HOST:5432\u002FDBNAME \\\n  django-cloudrun-test\n",[20,18869,18870,18883,18900,18913,18922,18931,18940],{"__ignoreMap":111},[115,18871,18872,18874,18876,18878,18881],{"class":117,"line":118},[115,18873,3295],{"class":262},[115,18875,17022],{"class":132},[115,18877,3909],{"class":202},[115,18879,18880],{"class":132}," django-cloudrun-test",[115,18882,17030],{"class":132},[115,18884,18885,18887,18890,18893,18895,18898],{"class":117,"line":136},[115,18886,3295],{"class":262},[115,18888,18889],{"class":132}," run",[115,18891,18892],{"class":202}," --rm",[115,18894,1001],{"class":202},[115,18896,18897],{"class":132}," 8080:8080",[115,18899,317],{"class":202},[115,18901,18902,18905,18908,18911],{"class":117,"line":149},[115,18903,18904],{"class":202},"  -e",[115,18906,18907],{"class":132}," PORT=",[115,18909,18910],{"class":202},"8080",[115,18912,317],{"class":202},[115,18914,18915,18917,18920],{"class":117,"line":162},[115,18916,18904],{"class":202},[115,18918,18919],{"class":132}," SECRET_KEY=test-secret",[115,18921,317],{"class":202},[115,18923,18924,18926,18929],{"class":117,"line":175},[115,18925,18904],{"class":202},[115,18927,18928],{"class":132}," ALLOWED_HOSTS=localhost,127.0.0.1",[115,18930,317],{"class":202},[115,18932,18933,18935,18938],{"class":117,"line":350},[115,18934,18904],{"class":202},[115,18936,18937],{"class":132}," DATABASE_URL=postgres:\u002F\u002FUSER:PASSWORD@HOST:5432\u002FDBNAME",[115,18939,317],{"class":202},[115,18941,18942],{"class":117,"line":365},[115,18943,18944],{"class":132},"  django-cloudrun-test\n",[16,18946,18947,18948,18950],{},"If your app can answer ",[20,18949,13411],{}," without a live database connection, local startup may still succeed with a placeholder database setting. If startup imports or checks the database eagerly, use a real reachable database for this test.",[16,18952,18953],{},"If the container does not start locally, fix that before using Cloud Run.",[11,18955,18957],{"id":18956},"_4-build-and-push-the-image-to-artifact-registry","4) Build and push the image to Artifact Registry",[16,18959,18960],{},"Set the project and enable required APIs:",[106,18962,18964],{"className":108,"code":18963,"language":110,"meta":111,"style":111},"gcloud auth login\ngcloud config set project YOUR_PROJECT_ID\ngcloud services enable \\\n  run.googleapis.com \\\n  artifactregistry.googleapis.com \\\n  secretmanager.googleapis.com \\\n  sqladmin.googleapis.com\n",[20,18965,18966,18975,18990,19001,19008,19015,19022],{"__ignoreMap":111},[115,18967,18968,18971,18973],{"class":117,"line":118},[115,18969,18970],{"class":262},"gcloud",[115,18972,17055],{"class":132},[115,18974,17058],{"class":132},[115,18976,18977,18979,18982,18984,18987],{"class":117,"line":136},[115,18978,18970],{"class":262},[115,18980,18981],{"class":132}," config",[115,18983,17470],{"class":132},[115,18985,18986],{"class":132}," project",[115,18988,18989],{"class":132}," YOUR_PROJECT_ID\n",[115,18991,18992,18994,18997,18999],{"class":117,"line":149},[115,18993,18970],{"class":262},[115,18995,18996],{"class":132}," services",[115,18998,8567],{"class":132},[115,19000,317],{"class":202},[115,19002,19003,19006],{"class":117,"line":162},[115,19004,19005],{"class":132},"  run.googleapis.com",[115,19007,317],{"class":202},[115,19009,19010,19013],{"class":117,"line":175},[115,19011,19012],{"class":132},"  artifactregistry.googleapis.com",[115,19014,317],{"class":202},[115,19016,19017,19020],{"class":117,"line":350},[115,19018,19019],{"class":132},"  secretmanager.googleapis.com",[115,19021,317],{"class":202},[115,19023,19024],{"class":117,"line":365},[115,19025,19026],{"class":132},"  sqladmin.googleapis.com\n",[16,19028,19029],{},"Create the repository:",[106,19031,19033],{"className":108,"code":19032,"language":110,"meta":111,"style":111},"gcloud artifacts repositories create django-repo \\\n  --repository-format=docker \\\n  --location=REGION\n",[20,19034,19035,19053,19060],{"__ignoreMap":111},[115,19036,19037,19039,19042,19045,19048,19051],{"class":117,"line":118},[115,19038,18970],{"class":262},[115,19040,19041],{"class":132}," artifacts",[115,19043,19044],{"class":132}," repositories",[115,19046,19047],{"class":132}," create",[115,19049,19050],{"class":132}," django-repo",[115,19052,317],{"class":202},[115,19054,19055,19058],{"class":117,"line":136},[115,19056,19057],{"class":202},"  --repository-format=docker",[115,19059,317],{"class":202},[115,19061,19062],{"class":117,"line":149},[115,19063,19064],{"class":202},"  --location=REGION\n",[16,19066,19067],{},"Configure Docker auth:",[106,19069,19071],{"className":108,"code":19070,"language":110,"meta":111,"style":111},"gcloud auth configure-docker REGION-docker.pkg.dev\n",[20,19072,19073],{"__ignoreMap":111},[115,19074,19075,19077,19079,19082],{"class":117,"line":118},[115,19076,18970],{"class":262},[115,19078,17055],{"class":132},[115,19080,19081],{"class":132}," configure-docker",[115,19083,19084],{"class":132}," REGION-docker.pkg.dev\n",[16,19086,19087],{},"Build and push:",[106,19089,19091],{"className":108,"code":19090,"language":110,"meta":111,"style":111},"docker build -t REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG .\ndocker push REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG\n",[20,19092,19093,19106],{"__ignoreMap":111},[115,19094,19095,19097,19099,19101,19104],{"class":117,"line":118},[115,19096,3295],{"class":262},[115,19098,17022],{"class":132},[115,19100,3909],{"class":202},[115,19102,19103],{"class":132}," REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG",[115,19105,17030],{"class":132},[115,19107,19108,19110,19113],{"class":117,"line":136},[115,19109,3295],{"class":262},[115,19111,19112],{"class":132}," push",[115,19114,19115],{"class":132}," REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG\n",[16,19117,19118],{},"Verify the image exists:",[106,19120,19122],{"className":108,"code":19121,"language":110,"meta":111,"style":111},"gcloud artifacts docker images list REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\n",[20,19123,19124],{"__ignoreMap":111},[115,19125,19126,19128,19130,19133,19136,19139],{"class":117,"line":118},[115,19127,18970],{"class":262},[115,19129,19041],{"class":132},[115,19131,19132],{"class":132}," docker",[115,19134,19135],{"class":132}," images",[115,19137,19138],{"class":132}," list",[115,19140,19141],{"class":132}," REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\n",[16,19143,19144,19145,19148],{},"Use release-specific tags, not only ",[20,19146,19147],{},"latest",". That makes rollback simpler.",[11,19150,19152],{"id":19151},"_5-provision-the-production-database-and-secrets","5) Provision the production database and secrets",[16,19154,19155],{},"Create a Cloud SQL PostgreSQL instance using the Google Cloud console or CLI, then create the database and application user.",[16,19157,19158],{},"Store secrets in Secret Manager:",[106,19160,19162],{"className":108,"code":19161,"language":110,"meta":111,"style":111},"echo -n \"your-django-secret-key\" | gcloud secrets create django-secret-key --data-file=-\necho -n \"postgres:\u002F\u002FDB_USER:DB_PASSWORD@\u002FDB_NAME?host=\u002Fcloudsql\u002FPROJECT:REGION:INSTANCE\" | gcloud secrets create django-database-url --data-file=-\n",[20,19163,19164,19188],{"__ignoreMap":111},[115,19165,19166,19168,19170,19173,19175,19178,19180,19182,19185],{"class":117,"line":118},[115,19167,1085],{"class":202},[115,19169,2794],{"class":202},[115,19171,19172],{"class":132}," \"your-django-secret-key\"",[115,19174,579],{"class":121},[115,19176,19177],{"class":262}," gcloud",[115,19179,17401],{"class":132},[115,19181,19047],{"class":132},[115,19183,19184],{"class":132}," django-secret-key",[115,19186,19187],{"class":202}," --data-file=-\n",[115,19189,19190,19192,19194,19197,19199,19201,19203,19205,19208],{"class":117,"line":136},[115,19191,1085],{"class":202},[115,19193,2794],{"class":202},[115,19195,19196],{"class":132}," \"postgres:\u002F\u002FDB_USER:DB_PASSWORD@\u002FDB_NAME?host=\u002Fcloudsql\u002FPROJECT:REGION:INSTANCE\"",[115,19198,579],{"class":121},[115,19200,19177],{"class":262},[115,19202,17401],{"class":132},[115,19204,19047],{"class":132},[115,19206,19207],{"class":132}," django-database-url",[115,19209,19187],{"class":202},[16,19211,19212,19213,19215],{},"That ",[20,19214,10873],{}," format uses the Cloud SQL Unix socket path, which is the normal pattern for Cloud Run + Cloud SQL.",[16,19217,19218],{},"Use a dedicated service account for the Cloud Run service and grant only the permissions it needs. At minimum, the runtime service account typically needs:",[63,19220,19221,19226],{},[66,19222,19223],{},[20,19224,19225],{},"roles\u002Fsecretmanager.secretAccessor",[66,19227,19228],{},[20,19229,19230],{},"roles\u002Fcloudsql.client",[16,19232,3515],{},[63,19234,19235,19238,19241],{},[66,19236,19237],{},"the secret names exist in Secret Manager",[66,19239,19240],{},"the database user can connect",[66,19242,19243,19244],{},"the Cloud SQL instance connection name matches ",[20,19245,19246],{},"PROJECT:REGION:INSTANCE",[11,19248,19250],{"id":19249},"_6-deploy-django-to-cloud-run","6) Deploy Django to Cloud Run",[16,19252,19253,19254,19256],{},"First deploy the service with secrets, Cloud SQL attachment, and the settings module. Do not guess the Cloud Run hostname for ",[20,19255,2719],{},"; get the actual generated URL after deploy.",[106,19258,19260],{"className":108,"code":19259,"language":110,"meta":111,"style":111},"gcloud run deploy django-app \\\n  --image REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG \\\n  --region REGION \\\n  --platform managed \\\n  --allow-unauthenticated \\\n  --service-account django-cloudrun@YOUR_PROJECT_ID.iam.gserviceaccount.com \\\n  --add-cloudsql-instances PROJECT:REGION:INSTANCE \\\n  --set-env-vars DJANGO_SETTINGS_MODULE=config.settings.production \\\n  --set-secrets SECRET_KEY=django-secret-key:latest \\\n  --set-secrets DATABASE_URL=django-database-url:latest\n",[20,19261,19262,19276,19285,19295,19305,19312,19322,19332,19342,19352],{"__ignoreMap":111},[115,19263,19264,19266,19268,19271,19274],{"class":117,"line":118},[115,19265,18970],{"class":262},[115,19267,18889],{"class":132},[115,19269,19270],{"class":132}," deploy",[115,19272,19273],{"class":132}," django-app",[115,19275,317],{"class":202},[115,19277,19278,19281,19283],{"class":117,"line":136},[115,19279,19280],{"class":202},"  --image",[115,19282,19103],{"class":132},[115,19284,317],{"class":202},[115,19286,19287,19290,19293],{"class":117,"line":149},[115,19288,19289],{"class":202},"  --region",[115,19291,19292],{"class":132}," REGION",[115,19294,317],{"class":202},[115,19296,19297,19300,19303],{"class":117,"line":162},[115,19298,19299],{"class":202},"  --platform",[115,19301,19302],{"class":132}," managed",[115,19304,317],{"class":202},[115,19306,19307,19310],{"class":117,"line":175},[115,19308,19309],{"class":202},"  --allow-unauthenticated",[115,19311,317],{"class":202},[115,19313,19314,19317,19320],{"class":117,"line":350},[115,19315,19316],{"class":202},"  --service-account",[115,19318,19319],{"class":132}," django-cloudrun@YOUR_PROJECT_ID.iam.gserviceaccount.com",[115,19321,317],{"class":202},[115,19323,19324,19327,19330],{"class":117,"line":365},[115,19325,19326],{"class":202},"  --add-cloudsql-instances",[115,19328,19329],{"class":132}," PROJECT:REGION:INSTANCE",[115,19331,317],{"class":202},[115,19333,19334,19337,19340],{"class":117,"line":380},[115,19335,19336],{"class":202},"  --set-env-vars",[115,19338,19339],{"class":132}," DJANGO_SETTINGS_MODULE=config.settings.production",[115,19341,317],{"class":202},[115,19343,19344,19347,19350],{"class":117,"line":487},[115,19345,19346],{"class":202},"  --set-secrets",[115,19348,19349],{"class":132}," SECRET_KEY=django-secret-key:latest",[115,19351,317],{"class":202},[115,19353,19354,19356],{"class":117,"line":2095},[115,19355,19346],{"class":202},[115,19357,19358],{"class":132}," DATABASE_URL=django-database-url:latest\n",[16,19360,19361],{},"Capture the service URL and update allowed hosts and CSRF origins:",[106,19363,19365],{"className":108,"code":19364,"language":110,"meta":111,"style":111},"SERVICE_URL=$(gcloud run services describe django-app --region REGION --format='value(status.url)')\nSERVICE_HOST=$(echo \"$SERVICE_URL\" | sed 's#https:\u002F\u002F##')\n\ngcloud run services update django-app \\\n  --region REGION \\\n  --set-env-vars ALLOWED_HOSTS=${SERVICE_HOST},yourdomain.com \\\n  --set-env-vars CSRF_TRUSTED_ORIGINS=${SERVICE_URL},https:\u002F\u002Fyourdomain.com\n",[20,19366,19367,19401,19429,19433,19447,19455,19470],{"__ignoreMap":111},[115,19368,19369,19372,19374,19377,19379,19381,19383,19386,19388,19391,19393,19396,19399],{"class":117,"line":118},[115,19370,19371],{"class":125},"SERVICE_URL",[115,19373,129],{"class":121},[115,19375,19376],{"class":125},"$(",[115,19378,18970],{"class":262},[115,19380,18889],{"class":132},[115,19382,18996],{"class":132},[115,19384,19385],{"class":132}," describe",[115,19387,19273],{"class":132},[115,19389,19390],{"class":202}," --region",[115,19392,19292],{"class":132},[115,19394,19395],{"class":202}," --format=",[115,19397,19398],{"class":132},"'value(status.url)'",[115,19400,2394],{"class":125},[115,19402,19403,19406,19408,19410,19412,19414,19417,19419,19421,19424,19427],{"class":117,"line":136},[115,19404,19405],{"class":125},"SERVICE_HOST",[115,19407,129],{"class":121},[115,19409,19376],{"class":125},[115,19411,1085],{"class":202},[115,19413,325],{"class":132},[115,19415,19416],{"class":125},"$SERVICE_URL",[115,19418,331],{"class":132},[115,19420,579],{"class":121},[115,19422,19423],{"class":262}," sed",[115,19425,19426],{"class":132}," 's#https:\u002F\u002F##'",[115,19428,2394],{"class":125},[115,19430,19431],{"class":117,"line":149},[115,19432,310],{"emptyLinePlaceholder":309},[115,19434,19435,19437,19439,19441,19443,19445],{"class":117,"line":162},[115,19436,18970],{"class":262},[115,19438,18889],{"class":132},[115,19440,18996],{"class":132},[115,19442,14305],{"class":132},[115,19444,19273],{"class":132},[115,19446,317],{"class":202},[115,19448,19449,19451,19453],{"class":117,"line":175},[115,19450,19289],{"class":202},[115,19452,19292],{"class":132},[115,19454,317],{"class":202},[115,19456,19457,19459,19462,19465,19468],{"class":117,"line":350},[115,19458,19336],{"class":202},[115,19460,19461],{"class":132}," ALLOWED_HOSTS=",[115,19463,19464],{"class":125},"${SERVICE_HOST}",[115,19466,19467],{"class":132},",yourdomain.com",[115,19469,317],{"class":202},[115,19471,19472,19474,19477,19480],{"class":117,"line":365},[115,19473,19336],{"class":202},[115,19475,19476],{"class":132}," CSRF_TRUSTED_ORIGINS=",[115,19478,19479],{"class":125},"${SERVICE_URL}",[115,19481,19482],{"class":132},",https:\u002F\u002Fyourdomain.com\n",[16,19484,19485],{},"You can also set memory, concurrency, and minimum instances:",[106,19487,19489],{"className":108,"code":19488,"language":110,"meta":111,"style":111},"gcloud run services update django-app \\\n  --region REGION \\\n  --memory 512Mi \\\n  --concurrency 40 \\\n  --min-instances 1\n",[20,19490,19491,19505,19513,19523,19533],{"__ignoreMap":111},[115,19492,19493,19495,19497,19499,19501,19503],{"class":117,"line":118},[115,19494,18970],{"class":262},[115,19496,18889],{"class":132},[115,19498,18996],{"class":132},[115,19500,14305],{"class":132},[115,19502,19273],{"class":132},[115,19504,317],{"class":202},[115,19506,19507,19509,19511],{"class":117,"line":136},[115,19508,19289],{"class":202},[115,19510,19292],{"class":132},[115,19512,317],{"class":202},[115,19514,19515,19518,19521],{"class":117,"line":149},[115,19516,19517],{"class":202},"  --memory",[115,19519,19520],{"class":132}," 512Mi",[115,19522,317],{"class":202},[115,19524,19525,19528,19531],{"class":117,"line":162},[115,19526,19527],{"class":202},"  --concurrency",[115,19529,19530],{"class":202}," 40",[115,19532,317],{"class":202},[115,19534,19535,19538],{"class":117,"line":175},[115,19536,19537],{"class":202},"  --min-instances",[115,19539,8995],{"class":202},[16,19541,55,19542,19545],{},[20,19543,19544],{},"min-instances=1"," if cold starts are a problem and the extra baseline cost is acceptable.",[16,19547,3515],{},[63,19549,19550,19553,19556,19563],{},[66,19551,19552],{},"service deploys successfully",[66,19554,19555],{},"a new revision becomes ready",[66,19557,19558,19560,19561],{},[20,19559,13411],{}," responds with ",[20,19562,17741],{},[66,19564,19565],{},"logs show no import, settings, secret, or database errors",[16,19567,19568,19569,211],{},"If the service should not be public, do not use ",[20,19570,19571],{},"--allow-unauthenticated",[11,19573,19575],{"id":19574},"_7-run-database-migrations-safely","7) Run database migrations safely",[16,19577,7471,19578,19580],{},[1226,19579,7474],{}," run migrations automatically on every container startup. Cloud Run can start multiple instances, and startup-time migrations create race conditions and fragile releases.",[16,19582,19583,19584,19587],{},"Use a separate controlled step. A practical Cloud Run-compatible option is a ",[1226,19585,19586],{},"Cloud Run Job"," using the same image, settings, secrets, and Cloud SQL connection.",[16,19589,19590],{},"Create the job:",[106,19592,19594],{"className":108,"code":19593,"language":110,"meta":111,"style":111},"gcloud run jobs create django-migrate \\\n  --image REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG \\\n  --region REGION \\\n  --service-account django-cloudrun@YOUR_PROJECT_ID.iam.gserviceaccount.com \\\n  --add-cloudsql-instances PROJECT:REGION:INSTANCE \\\n  --set-env-vars DJANGO_SETTINGS_MODULE=config.settings.production \\\n  --set-secrets SECRET_KEY=django-secret-key:latest \\\n  --set-secrets DATABASE_URL=django-database-url:latest \\\n  --command python \\\n  --args manage.py,migrate,--noinput\n",[20,19595,19596,19612,19620,19628,19636,19644,19652,19660,19669,19679],{"__ignoreMap":111},[115,19597,19598,19600,19602,19605,19607,19610],{"class":117,"line":118},[115,19599,18970],{"class":262},[115,19601,18889],{"class":132},[115,19603,19604],{"class":132}," jobs",[115,19606,19047],{"class":132},[115,19608,19609],{"class":132}," django-migrate",[115,19611,317],{"class":202},[115,19613,19614,19616,19618],{"class":117,"line":136},[115,19615,19280],{"class":202},[115,19617,19103],{"class":132},[115,19619,317],{"class":202},[115,19621,19622,19624,19626],{"class":117,"line":149},[115,19623,19289],{"class":202},[115,19625,19292],{"class":132},[115,19627,317],{"class":202},[115,19629,19630,19632,19634],{"class":117,"line":162},[115,19631,19316],{"class":202},[115,19633,19319],{"class":132},[115,19635,317],{"class":202},[115,19637,19638,19640,19642],{"class":117,"line":175},[115,19639,19326],{"class":202},[115,19641,19329],{"class":132},[115,19643,317],{"class":202},[115,19645,19646,19648,19650],{"class":117,"line":350},[115,19647,19336],{"class":202},[115,19649,19339],{"class":132},[115,19651,317],{"class":202},[115,19653,19654,19656,19658],{"class":117,"line":365},[115,19655,19346],{"class":202},[115,19657,19349],{"class":132},[115,19659,317],{"class":202},[115,19661,19662,19664,19667],{"class":117,"line":380},[115,19663,19346],{"class":202},[115,19665,19666],{"class":132}," DATABASE_URL=django-database-url:latest",[115,19668,317],{"class":202},[115,19670,19671,19674,19677],{"class":117,"line":487},[115,19672,19673],{"class":202},"  --command",[115,19675,19676],{"class":132}," python",[115,19678,317],{"class":202},[115,19680,19681,19684],{"class":117,"line":2095},[115,19682,19683],{"class":202},"  --args",[115,19685,19686],{"class":132}," manage.py,migrate,--noinput\n",[16,19688,19689],{},"Run the job:",[106,19691,19693],{"className":108,"code":19692,"language":110,"meta":111,"style":111},"gcloud run jobs execute django-migrate --region REGION --wait\n",[20,19694,19695],{"__ignoreMap":111},[115,19696,19697,19699,19701,19703,19706,19708,19710,19712],{"class":117,"line":118},[115,19698,18970],{"class":262},[115,19700,18889],{"class":132},[115,19702,19604],{"class":132},[115,19704,19705],{"class":132}," execute",[115,19707,19609],{"class":132},[115,19709,19390],{"class":202},[115,19711,19292],{"class":132},[115,19713,19714],{"class":202}," --wait\n",[16,19716,19717],{},"For later releases, update the job image before running it again:",[106,19719,19721],{"className":108,"code":19720,"language":110,"meta":111,"style":111},"gcloud run jobs update django-migrate \\\n  --image REGION-docker.pkg.dev\u002FYOUR_PROJECT_ID\u002Fdjango-repo\u002Fdjango-app:REVISION_TAG \\\n  --region REGION\n",[20,19722,19723,19737,19745],{"__ignoreMap":111},[115,19724,19725,19727,19729,19731,19733,19735],{"class":117,"line":118},[115,19726,18970],{"class":262},[115,19728,18889],{"class":132},[115,19730,19604],{"class":132},[115,19732,14305],{"class":132},[115,19734,19609],{"class":132},[115,19736,317],{"class":202},[115,19738,19739,19741,19743],{"class":117,"line":136},[115,19740,19280],{"class":202},[115,19742,19103],{"class":132},[115,19744,317],{"class":202},[115,19746,19747,19749],{"class":117,"line":149},[115,19748,19289],{"class":202},[115,19750,19751],{"class":132}," REGION\n",[16,19753,19754],{},"Before sending production traffic to a new release, verify:",[63,19756,19757,19760,19763],{},[66,19758,19759],{},"migrations completed successfully",[66,19761,19762],{},"schema is compatible with the new code",[66,19764,19765],{},"database-backed pages load correctly",[16,19767,19768],{},"Keep migrations backward-compatible where possible so rollback remains possible.",[11,19770,19772],{"id":19771},"_8-verify-the-deployment","8) Verify the deployment",[16,19774,19775],{},"Check service and revision state:",[106,19777,19779],{"className":108,"code":19778,"language":110,"meta":111,"style":111},"gcloud run services describe django-app --region REGION\ngcloud run revisions list --service django-app --region REGION\n",[20,19780,19781,19797],{"__ignoreMap":111},[115,19782,19783,19785,19787,19789,19791,19793,19795],{"class":117,"line":118},[115,19784,18970],{"class":262},[115,19786,18889],{"class":132},[115,19788,18996],{"class":132},[115,19790,19385],{"class":132},[115,19792,19273],{"class":132},[115,19794,19390],{"class":202},[115,19796,19751],{"class":132},[115,19798,19799,19801,19803,19806,19808,19811,19813,19815],{"class":117,"line":136},[115,19800,18970],{"class":262},[115,19802,18889],{"class":132},[115,19804,19805],{"class":132}," revisions",[115,19807,19138],{"class":132},[115,19809,19810],{"class":202}," --service",[115,19812,19273],{"class":132},[115,19814,19390],{"class":202},[115,19816,19751],{"class":132},[16,19818,12724],{},[106,19820,19822],{"className":108,"code":19821,"language":110,"meta":111,"style":111},"gcloud logs read \"resource.type=cloud_run_revision AND resource.labels.service_name=django-app\" --limit 50\n",[20,19823,19824],{"__ignoreMap":111},[115,19825,19826,19828,19830,19833,19836,19839],{"class":117,"line":118},[115,19827,18970],{"class":262},[115,19829,3301],{"class":132},[115,19831,19832],{"class":132}," read",[115,19834,19835],{"class":132}," \"resource.type=cloud_run_revision AND resource.labels.service_name=django-app\"",[115,19837,19838],{"class":202}," --limit",[115,19840,19841],{"class":202}," 50\n",[16,19843,19844],{},"Test the app:",[106,19846,19848],{"className":108,"code":19847,"language":110,"meta":111,"style":111},"curl \"${SERVICE_URL}\u002Fhealth\u002F\"\ncurl -I \"${SERVICE_URL}\u002Fstatic\u002FYOUR_FILE.css\"\n",[20,19849,19850,19862],{"__ignoreMap":111},[115,19851,19852,19854,19857,19859],{"class":117,"line":118},[115,19853,2764],{"class":262},[115,19855,19856],{"class":132}," \"${",[115,19858,19371],{"class":125},[115,19860,19861],{"class":132},"}\u002Fhealth\u002F\"\n",[115,19863,19864,19866,19868,19870,19872],{"class":117,"line":136},[115,19865,2764],{"class":262},[115,19867,2767],{"class":202},[115,19869,19856],{"class":132},[115,19871,19371],{"class":125},[115,19873,19874],{"class":132},"}\u002Fstatic\u002FYOUR_FILE.css\"\n",[16,19876,19877],{},"Smoke test these paths:",[63,19879,19880,19883,19886,19889],{},[66,19881,19882],{},"home page",[66,19884,19885],{},"admin login if used",[66,19887,19888],{},"one database-backed page",[66,19890,19891],{},"one static asset URL",[16,19893,19894,19895,19898,19899,19901],{},"If static files return ",[20,19896,19897],{},"404",", confirm ",[20,19900,13689],{}," ran during the image build and that WhiteNoise is enabled in the production settings module.",[11,19903,19905],{"id":19904},"_9-configure-custom-domain-tls-and-basic-hardening","9) Configure custom domain, TLS, and basic hardening",[16,19907,19908],{},"Map your custom domain in Cloud Run, then add DNS records as instructed by Google Cloud. Cloud Run handles TLS termination for mapped domains.",[16,19910,19911],{},"After domain mapping:",[63,19913,19914,19919,19924],{},[66,19915,19916,19917],{},"add the custom host to ",[20,19918,2719],{},[66,19920,19921,19922],{},"add the full HTTPS origin to ",[20,19923,2725],{},[66,19925,19926],{},"verify redirects and secure cookies work correctly",[16,19928,19929],{},"Review ingress settings and service account permissions. If the service should only be reachable internally or through another layer, adjust ingress and authentication accordingly.",[11,19931,19933],{"id":19932},"_10-rollback-and-recovery","10) Rollback and recovery",[16,19935,19936],{},"List revisions:",[106,19938,19940],{"className":108,"code":19939,"language":110,"meta":111,"style":111},"gcloud run revisions list --service django-app --region REGION\n",[20,19941,19942],{"__ignoreMap":111},[115,19943,19944,19946,19948,19950,19952,19954,19956,19958],{"class":117,"line":118},[115,19945,18970],{"class":262},[115,19947,18889],{"class":132},[115,19949,19805],{"class":132},[115,19951,19138],{"class":132},[115,19953,19810],{"class":202},[115,19955,19273],{"class":132},[115,19957,19390],{"class":202},[115,19959,19751],{"class":132},[16,19961,19962],{},"Shift traffic back to a previous good revision:",[106,19964,19966],{"className":108,"code":19965,"language":110,"meta":111,"style":111},"gcloud run services update-traffic django-app \\\n  --to-revisions REVISION_NAME=100 \\\n  --region REGION\n",[20,19967,19968,19983,19996],{"__ignoreMap":111},[115,19969,19970,19972,19974,19976,19979,19981],{"class":117,"line":118},[115,19971,18970],{"class":262},[115,19973,18889],{"class":132},[115,19975,18996],{"class":132},[115,19977,19978],{"class":132}," update-traffic",[115,19980,19273],{"class":132},[115,19982,317],{"class":202},[115,19984,19985,19988,19991,19994],{"class":117,"line":136},[115,19986,19987],{"class":202},"  --to-revisions",[115,19989,19990],{"class":132}," REVISION_NAME=",[115,19992,19993],{"class":202},"100",[115,19995,317],{"class":202},[115,19997,19998,20000],{"class":117,"line":149},[115,19999,19289],{"class":202},[115,20001,19751],{"class":132},[16,20003,20004],{},"After rollback, verify:",[63,20006,20007,20010,20017,20020],{},[66,20008,20009],{},"traffic allocation points to the intended revision",[66,20011,20012,20014,20015],{},[20,20013,13411],{}," returns ",[20,20016,17741],{},[66,20018,20019],{},"a database-backed page loads",[66,20021,20022],{},"logs no longer show the release error",[16,20024,20025],{},"If the release failed because of a migration, a traffic rollback alone may not be enough. You may also need a database recovery step. This is why releases should use:",[63,20027,20028,20031,20034],{},[66,20029,20030],{},"immutable image tags",[66,20032,20033],{},"backward-compatible migrations",[66,20035,20036],{},"staged verification before full traffic cutover",[11,20038,1321],{"id":1320},[16,20040,20041],{},"This setup works because it matches Cloud Run’s execution model. Django runs as a stateless HTTP app behind a managed HTTPS proxy. Gunicorn binds to the injected port, WhiteNoise serves static assets bundled into the image, Cloud SQL provides persistent relational storage, and secrets stay out of the image and source code.",[16,20043,20044],{},"Cloud Run is a good fit for Django applications that can stay stateless at the web layer. It is less suitable when the app depends on local persistent files, long-running request work, or in-process background jobs. In those cases, combine Cloud Run with object storage, task queues, and worker services, or choose a different hosting model.",[11,20046,1337],{"id":1336},[52,20048,20050],{"id":20049},"static-files-returning-404","Static files returning 404",[16,20052,20053],{},"Usually caused by one of these:",[63,20055,20056,20061,20064,20069],{},[66,20057,20058,20060],{},[20,20059,13689],{}," did not run during build",[66,20062,20063],{},"WhiteNoise middleware is missing",[66,20065,20066,20068],{},[20,20067,11918],{}," is wrong",[66,20070,20071],{},"the wrong settings module is being used",[52,20073,20075],{"id":20074},"media-uploads-do-not-persist","Media uploads do not persist",[16,20077,20078,20079,211],{},"Cloud Run container storage is ephemeral. Store uploads in object storage, not ",[20,20080,20081],{},"\u002Fapp\u002Fmedia",[52,20083,20085],{"id":20084},"database-connection-failures","Database connection failures",[16,20087,20088],{},"Common causes:",[63,20090,20091,20094,20100,20105],{},[66,20092,20093],{},"wrong Cloud SQL instance connection name",[66,20095,20096,20097],{},"service or job not attached with ",[20,20098,20099],{},"--add-cloudsql-instances",[66,20101,20102,20103],{},"invalid socket-based ",[20,20104,10873],{},[66,20106,20107,20108,20110],{},"missing ",[20,20109,19230],{}," on the runtime service account",[52,20112,20114],{"id":20113},"secret-injection-failures","Secret injection failures",[16,20116,20088],{},[63,20118,20119,20122,20126],{},[66,20120,20121],{},"wrong secret name or version reference",[66,20123,20107,20124,20110],{},[20,20125,19225],{},[66,20127,20128],{},"deploying with a different service account than expected",[52,20130,20132],{"id":20131},"request-timeout-or-slow-startup","Request timeout or slow startup",[16,20134,20135],{},"Slow imports, expensive startup logic, or too many worker processes can cause failures. Keep startup fast and move one-time tasks out of container boot.",[52,20137,20139],{"id":20138},"cold-starts-and-scaling-tradeoffs","Cold starts and scaling tradeoffs",[16,20141,20142,20143,20145],{},"Cloud Run scales well, but cold starts can affect latency. ",[20,20144,19544],{}," reduces that at the cost of always-on baseline usage.",[52,20147,2908],{"id":2907},[16,20149,20150],{},"Once you repeat this process across environments or services, script the build, push, deploy, secret injection, migration, and smoke test steps. Good template candidates are the Dockerfile, Django production settings, Cloud Run deploy command, and migration job definition.",[11,20152,1386],{"id":1385},[16,20154,20155,20156,211,20159,20162,20163,211,20165,20167,20168,211,20171,20173,20174,211],{},"For the settings side, see ",[1395,20157,20158],{"href":3006},"Django Environment Variables for Production",[20160,20161],"br",{},"\nFor a more traditional VM deployment path, compare ",[1395,20164,2986],{"href":2985},[20160,20166],{},"\nFor database hardening details, see ",[1395,20169,20170],{"href":1403},"Configure PostgreSQL for Django Production",[20160,20172],{},"\nFor release recovery procedures, use ",[1395,20175,20176],{"href":1415},"Django Deployment Rollback Checklist",[11,20178,1420],{"id":1419},[52,20180,20182],{"id":20181},"do-i-need-docker-to-deploy-django-on-google-cloud-run","Do I need Docker to deploy Django on Google Cloud Run?",[16,20184,20185],{},"Yes. Cloud Run runs containers, so your Django app must be packaged as a container image.",[52,20187,20189],{"id":20188},"how-should-i-serve-static-files-for-django-on-cloud-run","How should I serve static files for Django on Cloud Run?",[16,20191,20192],{},"For many apps, WhiteNoise inside the container is the simplest option. For larger assets or CDN-heavy setups, use object storage and a CDN.",[52,20194,20196],{"id":20195},"can-django-media-uploads-be-stored-on-the-cloud-run-filesystem","Can Django media uploads be stored on the Cloud Run filesystem?",[16,20198,20199],{},"No. The filesystem is ephemeral. Use persistent object storage for uploads.",[52,20201,20203],{"id":20202},"how-do-i-run-django-migrations-safely-on-cloud-run","How do I run Django migrations safely on Cloud Run?",[16,20205,20206],{},"Run migrations as a separate controlled step, not automatically in container startup. A Cloud Run Job using the same image, secrets, and Cloud SQL connection is a practical approach.",[52,20208,20210],{"id":20209},"what-is-the-fastest-way-to-roll-back-a-failed-cloud-run-deployment","What is the fastest way to roll back a failed Cloud Run deployment?",[16,20212,20213,20214,20217],{},"Shift traffic back to the previous healthy revision with ",[20,20215,20216],{},"gcloud run services update-traffic",". If the failure involved a database migration, review database recovery separately.",[1485,20219,20220],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":111,"searchDepth":149,"depth":149,"links":20222},[20223,20224,20225,20226,20227,20228,20229,20230,20231,20232,20233,20234,20235,20236,20237,20246,20247],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":18108,"depth":136,"text":18109},{"id":18176,"depth":136,"text":18177},{"id":18718,"depth":136,"text":18719},{"id":18956,"depth":136,"text":18957},{"id":19151,"depth":136,"text":19152},{"id":19249,"depth":136,"text":19250},{"id":19574,"depth":136,"text":19575},{"id":19771,"depth":136,"text":19772},{"id":19904,"depth":136,"text":19905},{"id":19932,"depth":136,"text":19933},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":20238},[20239,20240,20241,20242,20243,20244,20245],{"id":20049,"depth":149,"text":20050},{"id":20074,"depth":149,"text":20075},{"id":20084,"depth":149,"text":20085},{"id":20113,"depth":149,"text":20114},{"id":20131,"depth":149,"text":20132},{"id":20138,"depth":149,"text":20139},{"id":2907,"depth":149,"text":2908},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":20248},[20249,20250,20251,20252,20253],{"id":20181,"depth":149,"text":20182},{"id":20188,"depth":149,"text":20189},{"id":20195,"depth":149,"text":20196},{"id":20202,"depth":149,"text":20203},{"id":20209,"depth":149,"text":20210},{},"\u002Fdeploy-django-on-google-cloud-run","14",[2992,20258,2985],"\u002Fdeploy\u002Fdockerize-django-production",{"title":18014,"description":18021},[1557,20261,3295],"gcp-cloud-run","deploy-django-on-google-cloud-run",[1557,20261,3295],"7IDmWSuNvrmMT9TT6fCtbark7U8bRmjFYkwgQtQRs7Y",{"id":20266,"title":20267,"body":20268,"category":3088,"description":23034,"difficulty":23035,"extension":1544,"funnel_stage":23036,"intent":1546,"meta":23037,"navigation":309,"path":23038,"priority":23039,"related":23040,"role":23035,"section":3098,"seo":23042,"stack":23043,"stem":23044,"tags":23045,"type":1561,"__hash__":23046},"articles\u002Fdjango-deploy-with-ansible.md","Deploy Django with Ansible: Step-by-Step",{"type":8,"value":20269,"toc":23004},[20270,20272,20275,20278,20289,20295,20298,20314,20316,20323,20374,20376,20380,20383,20412,20424,20428,20431,20437,20441,20446,20466,20472,20477,20666,20668,20671,20674,20689,20692,20766,20769,20773,20778,21134,21137,21158,21160,21175,21178,21182,21187,21511,21518,21523,21579,21585,21589,21595,21601,21842,21845,21871,21874,21877,21882,21981,21984,22138,22140,22169,22173,22176,22181,22367,22370,22581,22583,22615,22634,22638,22641,22644,22677,22680,22684,22687,22691,22694,22750,22753,22784,22788,22794,22830,22833,22836,22850,22853,22859,22861,22864,22871,22874,22888,22890,22924,22926,22933,22938,22943,22948,22953,22955,22959,22966,22970,22973,22977,22980,22984,22990,22994,23001],[11,20271,14],{"id":13},[16,20273,20274],{},"Manual Django deployment usually starts simple and becomes unreliable fast. You SSH into a server, pull code, install packages, update environment variables, run migrations, restart Gunicorn, reload Nginx, and hope you did not miss a step.",[16,20276,20277],{},"That breaks down in production for three reasons:",[63,20279,20280,20283,20286],{},[66,20281,20282],{},"servers drift over time",[66,20284,20285],{},"releases become hard to repeat exactly",[66,20287,20288],{},"rollback gets risky when changes are applied manually",[16,20290,11651,20291,20294],{},[1226,20292,20293],{},"deploy Django with Ansible"," safely, the goal is not just to copy files to a server. The goal is to automate a complete production path: install dependencies, place secrets securely, deploy code, run Django management commands in the right order, configure Gunicorn and Nginx, and verify the app is healthy before you consider the release done.",[16,20296,20297],{},"This guide shows a practical single-server pattern for Ubuntu or Debian using:",[63,20299,20300,20303,20305,20307,20309,20311],{},[66,20301,20302],{},"Django",[66,20304,1773],{},[66,20306,1946],{},[66,20308,1647],{},[66,20310,1277],{},[66,20312,20313],{},"Ansible from a separate control machine",[11,20315,30],{"id":29},[16,20317,20318,20319,20322],{},"A solid ",[1226,20320,20321],{},"Django deployment with Ansible"," usually looks like this:",[1173,20324,20325,20328,20331,20334,20337,20340,20351,20357,20360,20363,20371],{},[66,20326,20327],{},"define inventory and host variables",[66,20329,20330],{},"bootstrap the server with packages, users, and directories",[66,20332,20333],{},"deploy code from Git to a versioned release directory",[66,20335,20336],{},"create a virtualenv and install requirements",[66,20338,20339],{},"render a protected environment file from encrypted vars",[66,20341,7902,20342,1153,20344,20346,20347,20350],{},[20,20343,10296],{},[20,20345,13689],{},", and ",[20,20348,20349],{},"check --deploy"," with the production environment loaded",[66,20352,20353,20354,20356],{},"switch the ",[20,20355,13654],{}," symlink only after release checks succeed",[66,20358,20359],{},"configure Gunicorn with systemd",[66,20361,20362],{},"configure Nginx as a reverse proxy",[66,20364,20365,20366,1153,20368,20370],{},"validate the app with ",[20,20367,2764],{},[20,20369,1981],{},", logs, and static asset checks",[66,20372,20373],{},"keep at least one previous release available for rollback",[11,20375,43],{"id":42},[11,20377,20379],{"id":20378},"choose-the-target-architecture-before-writing-the-playbook","Choose the target architecture before writing the playbook",[16,20381,20382],{},"Use a simple production stack first:",[63,20384,20385,20388,20391,20397,20400,20406,20409],{},[66,20386,20387],{},"Ubuntu server",[66,20389,20390],{},"PostgreSQL local or managed",[66,20392,20393,20394],{},"Gunicorn bound to ",[20,20395,20396],{},"127.0.0.1:8000",[66,20398,20399],{},"Nginx listening on 80 and 443",[66,20401,20402,20403],{},"Django code deployed under ",[20,20404,20405],{},"\u002Fsrv\u002Fmyapp",[66,20407,20408],{},"systemd managing Gunicorn",[66,20410,20411],{},"Ansible running from your laptop or CI runner",[16,20413,20414,20415,1153,20418,20346,20421,211],{},"This guide assumes one application server. The same pattern extends to multiple hosts later by splitting groups like ",[20,20416,20417],{},"web",[20,20419,20420],{},"db",[20,20422,20423],{},"worker",[11,20425,20427],{"id":20426},"prepare-your-ansible-project-structure","Prepare your Ansible project structure",[16,20429,20430],{},"A clean layout helps keep bootstrap and deploy work separate:",[106,20432,20435],{"className":20433,"code":20434,"language":247,"meta":111},[245],"ansible\u002F\n├── inventory\u002F\n│   └── production.ini\n├── group_vars\u002F\n│   ├── production.yml\n│   └── vault.yml\n├── playbooks\u002F\n│   ├── bootstrap.yml\n│   └── deploy.yml\n└── templates\u002F\n    ├── gunicorn.service.j2\n    ├── nginx-site.conf.j2\n    └── env.j2\n",[20,20436,20434],{"__ignoreMap":111},[52,20438,20440],{"id":20439},"define-inventory-and-host-variables","Define inventory and host variables",[16,20442,20443],{},[20,20444,20445],{},"inventory\u002Fproduction.ini",[106,20447,20449],{"className":2026,"code":20448,"language":2028,"meta":111,"style":111},"[web]\napp1.example.com ansible_user=deploy\n",[20,20450,20451,20456],{"__ignoreMap":111},[115,20452,20453],{"class":117,"line":118},[115,20454,20455],{"class":262},"[web]\n",[115,20457,20458,20461,20464],{"class":117,"line":136},[115,20459,20460],{"class":125},"app1.example.com ",[115,20462,20463],{"class":121},"ansible_user",[115,20465,12548],{"class":125},[16,20467,20468,20469,20471],{},"This assumes the ",[20,20470,3098],{}," SSH user already exists on the server and has sudo access. If not, create it out of band or use your initial provisioner account for bootstrap.",[16,20473,20474],{},[20,20475,20476],{},"group_vars\u002Fproduction.yml",[106,20478,20480],{"className":2485,"code":20479,"language":2487,"meta":111,"style":111},"app_name: myapp\napp_user: myapp\napp_group: myapp\napp_domain: app1.example.com\n\ndeploy_root: \u002Fsrv\u002Fmyapp\nreleases_dir: \"{{ deploy_root }}\u002Freleases\"\nshared_dir: \"{{ deploy_root }}\u002Fshared\"\ncurrent_dir: \"{{ deploy_root }}\u002Fcurrent\"\nmedia_dir: \"{{ shared_dir }}\u002Fmedia\"\n\nrepo_url: \"git@github.com:yourorg\u002Fmyapp.git\"\ndeploy_version: \"main\"\n\nvenv_path: \"{{ shared_dir }}\u002Fvenv\"\nenv_file: \"{{ shared_dir }}\u002F.env\"\n\ndjango_project_dir: \"{{ current_dir }}\"\ndjango_wsgi_module: \"config.wsgi:application\"\ngunicorn_bind: \"127.0.0.1:8000\"\npython_executable: python3\n",[20,20481,20482,20492,20501,20510,20520,20524,20534,20544,20554,20564,20574,20578,20588,20598,20602,20612,20622,20626,20636,20646,20656],{"__ignoreMap":111},[115,20483,20484,20487,20489],{"class":117,"line":118},[115,20485,20486],{"class":2494},"app_name",[115,20488,2513],{"class":125},[115,20490,20491],{"class":132},"myapp\n",[115,20493,20494,20497,20499],{"class":117,"line":136},[115,20495,20496],{"class":2494},"app_user",[115,20498,2513],{"class":125},[115,20500,20491],{"class":132},[115,20502,20503,20506,20508],{"class":117,"line":149},[115,20504,20505],{"class":2494},"app_group",[115,20507,2513],{"class":125},[115,20509,20491],{"class":132},[115,20511,20512,20515,20517],{"class":117,"line":162},[115,20513,20514],{"class":2494},"app_domain",[115,20516,2513],{"class":125},[115,20518,20519],{"class":132},"app1.example.com\n",[115,20521,20522],{"class":117,"line":175},[115,20523,310],{"emptyLinePlaceholder":309},[115,20525,20526,20529,20531],{"class":117,"line":350},[115,20527,20528],{"class":2494},"deploy_root",[115,20530,2513],{"class":125},[115,20532,20533],{"class":132},"\u002Fsrv\u002Fmyapp\n",[115,20535,20536,20539,20541],{"class":117,"line":365},[115,20537,20538],{"class":2494},"releases_dir",[115,20540,2513],{"class":125},[115,20542,20543],{"class":132},"\"{{ deploy_root }}\u002Freleases\"\n",[115,20545,20546,20549,20551],{"class":117,"line":380},[115,20547,20548],{"class":2494},"shared_dir",[115,20550,2513],{"class":125},[115,20552,20553],{"class":132},"\"{{ deploy_root }}\u002Fshared\"\n",[115,20555,20556,20559,20561],{"class":117,"line":487},[115,20557,20558],{"class":2494},"current_dir",[115,20560,2513],{"class":125},[115,20562,20563],{"class":132},"\"{{ deploy_root }}\u002Fcurrent\"\n",[115,20565,20566,20569,20571],{"class":117,"line":2095},[115,20567,20568],{"class":2494},"media_dir",[115,20570,2513],{"class":125},[115,20572,20573],{"class":132},"\"{{ shared_dir }}\u002Fmedia\"\n",[115,20575,20576],{"class":117,"line":2104},[115,20577,310],{"emptyLinePlaceholder":309},[115,20579,20580,20583,20585],{"class":117,"line":2113},[115,20581,20582],{"class":2494},"repo_url",[115,20584,2513],{"class":125},[115,20586,20587],{"class":132},"\"git@github.com:yourorg\u002Fmyapp.git\"\n",[115,20589,20590,20593,20595],{"class":117,"line":2122},[115,20591,20592],{"class":2494},"deploy_version",[115,20594,2513],{"class":125},[115,20596,20597],{"class":132},"\"main\"\n",[115,20599,20600],{"class":117,"line":2131},[115,20601,310],{"emptyLinePlaceholder":309},[115,20603,20604,20607,20609],{"class":117,"line":2136},[115,20605,20606],{"class":2494},"venv_path",[115,20608,2513],{"class":125},[115,20610,20611],{"class":132},"\"{{ shared_dir }}\u002Fvenv\"\n",[115,20613,20614,20617,20619],{"class":117,"line":2142},[115,20615,20616],{"class":2494},"env_file",[115,20618,2513],{"class":125},[115,20620,20621],{"class":132},"\"{{ shared_dir }}\u002F.env\"\n",[115,20623,20624],{"class":117,"line":2273},[115,20625,310],{"emptyLinePlaceholder":309},[115,20627,20628,20631,20633],{"class":117,"line":2282},[115,20629,20630],{"class":2494},"django_project_dir",[115,20632,2513],{"class":125},[115,20634,20635],{"class":132},"\"{{ current_dir }}\"\n",[115,20637,20638,20641,20643],{"class":117,"line":2291},[115,20639,20640],{"class":2494},"django_wsgi_module",[115,20642,2513],{"class":125},[115,20644,20645],{"class":132},"\"config.wsgi:application\"\n",[115,20647,20648,20651,20653],{"class":117,"line":2299},[115,20649,20650],{"class":2494},"gunicorn_bind",[115,20652,2513],{"class":125},[115,20654,20655],{"class":132},"\"127.0.0.1:8000\"\n",[115,20657,20658,20661,20663],{"class":117,"line":2307},[115,20659,20660],{"class":2494},"python_executable",[115,20662,2513],{"class":125},[115,20664,20665],{"class":132},"python3\n",[52,20667,12382],{"id":12381},[16,20669,20670],{},"Do not commit Django secrets in plaintext. Use Ansible Vault or another secret backend.",[16,20672,20673],{},"Create the vault file:",[106,20675,20677],{"className":108,"code":20676,"language":110,"meta":111,"style":111},"ansible-vault create group_vars\u002Fvault.yml\n",[20,20678,20679],{"__ignoreMap":111},[115,20680,20681,20684,20686],{"class":117,"line":118},[115,20682,20683],{"class":262},"ansible-vault",[115,20685,19047],{"class":132},[115,20687,20688],{"class":132}," group_vars\u002Fvault.yml\n",[16,20690,20691],{},"Inside it:",[106,20693,20695],{"className":2485,"code":20694,"language":2487,"meta":111,"style":111},"django_secret_key: \"replace-me\"\ndb_name: \"myapp\"\ndb_user: \"myapp\"\ndb_password: \"replace-me\"\ndb_host: \"127.0.0.1\"\ndb_port: \"5432\"\ncsrf_trusted_origins:\n  - \"https:\u002F\u002Fapp1.example.com\"\n",[20,20696,20697,20706,20715,20724,20733,20742,20751,20758],{"__ignoreMap":111},[115,20698,20699,20702,20704],{"class":117,"line":118},[115,20700,20701],{"class":2494},"django_secret_key",[115,20703,2513],{"class":125},[115,20705,159],{"class":132},[115,20707,20708,20711,20713],{"class":117,"line":136},[115,20709,20710],{"class":2494},"db_name",[115,20712,2513],{"class":125},[115,20714,133],{"class":132},[115,20716,20717,20720,20722],{"class":117,"line":149},[115,20718,20719],{"class":2494},"db_user",[115,20721,2513],{"class":125},[115,20723,133],{"class":132},[115,20725,20726,20729,20731],{"class":117,"line":162},[115,20727,20728],{"class":2494},"db_password",[115,20730,2513],{"class":125},[115,20732,159],{"class":132},[115,20734,20735,20738,20740],{"class":117,"line":175},[115,20736,20737],{"class":2494},"db_host",[115,20739,2513],{"class":125},[115,20741,172],{"class":132},[115,20743,20744,20747,20749],{"class":117,"line":350},[115,20745,20746],{"class":2494},"db_port",[115,20748,2513],{"class":125},[115,20750,185],{"class":132},[115,20752,20753,20756],{"class":117,"line":365},[115,20754,20755],{"class":2494},"csrf_trusted_origins",[115,20757,2498],{"class":125},[115,20759,20760,20763],{"class":117,"line":380},[115,20761,20762],{"class":125},"  - ",[115,20764,20765],{"class":132},"\"https:\u002F\u002Fapp1.example.com\"\n",[16,20767,20768],{},"Your deployed environment file should be readable only by the app user.",[11,20770,20772],{"id":20771},"bootstrap-the-server-with-ansible","Bootstrap the server with Ansible",[16,20774,20775],{},[20,20776,20777],{},"playbooks\u002Fbootstrap.yml",[106,20779,20781],{"className":2485,"code":20780,"language":2487,"meta":111,"style":111},"- hosts: web\n  become: true\n  tasks:\n    - name: Install system packages\n      apt:\n        name:\n          - python3\n          - python3-venv\n          - python3-pip\n          - python3-dev\n          - build-essential\n          - libpq-dev\n          - git\n          - nginx\n          - ufw\n        state: present\n        update_cache: true\n\n    - name: Create app group\n      group:\n        name: \"{{ app_group }}\"\n        state: present\n\n    - name: Create app user\n      user:\n        name: \"{{ app_user }}\"\n        group: \"{{ app_group }}\"\n        system: true\n        shell: \u002Fusr\u002Fsbin\u002Fnologin\n        create_home: false\n\n    - name: Create deployment directories\n      file:\n        path: \"{{ item }}\"\n        state: directory\n        owner: \"{{ app_user }}\"\n        group: \"{{ app_group }}\"\n        mode: \"0755\"\n      loop:\n        - \"{{ deploy_root }}\"\n        - \"{{ releases_dir }}\"\n        - \"{{ shared_dir }}\"\n        - \"{{ media_dir }}\"\n",[20,20782,20783,20796,20806,20813,20826,20833,20840,20847,20854,20861,20868,20875,20882,20889,20896,20903,20913,20922,20926,20937,20944,20953,20961,20965,20976,20983,20992,21001,21010,21020,21030,21034,21045,21052,21062,21071,21080,21088,21098,21105,21113,21120,21127],{"__ignoreMap":111},[115,20784,20785,20788,20791,20793],{"class":117,"line":118},[115,20786,20787],{"class":125},"- ",[115,20789,20790],{"class":2494},"hosts",[115,20792,2513],{"class":125},[115,20794,20795],{"class":132},"web\n",[115,20797,20798,20801,20803],{"class":117,"line":136},[115,20799,20800],{"class":2494},"  become",[115,20802,2513],{"class":125},[115,20804,20805],{"class":202},"true\n",[115,20807,20808,20811],{"class":117,"line":149},[115,20809,20810],{"class":2494},"  tasks",[115,20812,2498],{"class":125},[115,20814,20815,20818,20821,20823],{"class":117,"line":162},[115,20816,20817],{"class":125},"    - ",[115,20819,20820],{"class":2494},"name",[115,20822,2513],{"class":125},[115,20824,20825],{"class":132},"Install system packages\n",[115,20827,20828,20831],{"class":117,"line":175},[115,20829,20830],{"class":2494},"      apt",[115,20832,2498],{"class":125},[115,20834,20835,20838],{"class":117,"line":350},[115,20836,20837],{"class":2494},"        name",[115,20839,2498],{"class":125},[115,20841,20842,20845],{"class":117,"line":365},[115,20843,20844],{"class":125},"          - ",[115,20846,20665],{"class":132},[115,20848,20849,20851],{"class":117,"line":380},[115,20850,20844],{"class":125},[115,20852,20853],{"class":132},"python3-venv\n",[115,20855,20856,20858],{"class":117,"line":487},[115,20857,20844],{"class":125},[115,20859,20860],{"class":132},"python3-pip\n",[115,20862,20863,20865],{"class":117,"line":2095},[115,20864,20844],{"class":125},[115,20866,20867],{"class":132},"python3-dev\n",[115,20869,20870,20872],{"class":117,"line":2104},[115,20871,20844],{"class":125},[115,20873,20874],{"class":132},"build-essential\n",[115,20876,20877,20879],{"class":117,"line":2113},[115,20878,20844],{"class":125},[115,20880,20881],{"class":132},"libpq-dev\n",[115,20883,20884,20886],{"class":117,"line":2122},[115,20885,20844],{"class":125},[115,20887,20888],{"class":132},"git\n",[115,20890,20891,20893],{"class":117,"line":2131},[115,20892,20844],{"class":125},[115,20894,20895],{"class":132},"nginx\n",[115,20897,20898,20900],{"class":117,"line":2136},[115,20899,20844],{"class":125},[115,20901,20902],{"class":132},"ufw\n",[115,20904,20905,20908,20910],{"class":117,"line":2142},[115,20906,20907],{"class":2494},"        state",[115,20909,2513],{"class":125},[115,20911,20912],{"class":132},"present\n",[115,20914,20915,20918,20920],{"class":117,"line":2273},[115,20916,20917],{"class":2494},"        update_cache",[115,20919,2513],{"class":125},[115,20921,20805],{"class":202},[115,20923,20924],{"class":117,"line":2282},[115,20925,310],{"emptyLinePlaceholder":309},[115,20927,20928,20930,20932,20934],{"class":117,"line":2291},[115,20929,20817],{"class":125},[115,20931,20820],{"class":2494},[115,20933,2513],{"class":125},[115,20935,20936],{"class":132},"Create app group\n",[115,20938,20939,20942],{"class":117,"line":2299},[115,20940,20941],{"class":2494},"      group",[115,20943,2498],{"class":125},[115,20945,20946,20948,20950],{"class":117,"line":2307},[115,20947,20837],{"class":2494},[115,20949,2513],{"class":125},[115,20951,20952],{"class":132},"\"{{ app_group }}\"\n",[115,20954,20955,20957,20959],{"class":117,"line":2315},[115,20956,20907],{"class":2494},[115,20958,2513],{"class":125},[115,20960,20912],{"class":132},[115,20962,20963],{"class":117,"line":2320},[115,20964,310],{"emptyLinePlaceholder":309},[115,20966,20967,20969,20971,20973],{"class":117,"line":7083},[115,20968,20817],{"class":125},[115,20970,20820],{"class":2494},[115,20972,2513],{"class":125},[115,20974,20975],{"class":132},"Create app user\n",[115,20977,20978,20981],{"class":117,"line":7090},[115,20979,20980],{"class":2494},"      user",[115,20982,2498],{"class":125},[115,20984,20985,20987,20989],{"class":117,"line":7097},[115,20986,20837],{"class":2494},[115,20988,2513],{"class":125},[115,20990,20991],{"class":132},"\"{{ app_user }}\"\n",[115,20993,20994,20997,20999],{"class":117,"line":7108},[115,20995,20996],{"class":2494},"        group",[115,20998,2513],{"class":125},[115,21000,20952],{"class":132},[115,21002,21003,21006,21008],{"class":117,"line":7113},[115,21004,21005],{"class":2494},"        system",[115,21007,2513],{"class":125},[115,21009,20805],{"class":202},[115,21011,21012,21015,21017],{"class":117,"line":16535},[115,21013,21014],{"class":2494},"        shell",[115,21016,2513],{"class":125},[115,21018,21019],{"class":132},"\u002Fusr\u002Fsbin\u002Fnologin\n",[115,21021,21022,21025,21027],{"class":117,"line":16544},[115,21023,21024],{"class":2494},"        create_home",[115,21026,2513],{"class":125},[115,21028,21029],{"class":202},"false\n",[115,21031,21032],{"class":117,"line":16549},[115,21033,310],{"emptyLinePlaceholder":309},[115,21035,21036,21038,21040,21042],{"class":117,"line":16555},[115,21037,20817],{"class":125},[115,21039,20820],{"class":2494},[115,21041,2513],{"class":125},[115,21043,21044],{"class":132},"Create deployment directories\n",[115,21046,21047,21050],{"class":117,"line":16564},[115,21048,21049],{"class":2494},"      file",[115,21051,2498],{"class":125},[115,21053,21054,21057,21059],{"class":117,"line":16573},[115,21055,21056],{"class":2494},"        path",[115,21058,2513],{"class":125},[115,21060,21061],{"class":132},"\"{{ item }}\"\n",[115,21063,21064,21066,21068],{"class":117,"line":16582},[115,21065,20907],{"class":2494},[115,21067,2513],{"class":125},[115,21069,21070],{"class":132},"directory\n",[115,21072,21073,21076,21078],{"class":117,"line":16587},[115,21074,21075],{"class":2494},"        owner",[115,21077,2513],{"class":125},[115,21079,20991],{"class":132},[115,21081,21082,21084,21086],{"class":117,"line":16596},[115,21083,20996],{"class":2494},[115,21085,2513],{"class":125},[115,21087,20952],{"class":132},[115,21089,21090,21093,21095],{"class":117,"line":16609},[115,21091,21092],{"class":2494},"        mode",[115,21094,2513],{"class":125},[115,21096,21097],{"class":132},"\"0755\"\n",[115,21099,21100,21103],{"class":117,"line":16614},[115,21101,21102],{"class":2494},"      loop",[115,21104,2498],{"class":125},[115,21106,21107,21110],{"class":117,"line":16624},[115,21108,21109],{"class":125},"        - ",[115,21111,21112],{"class":132},"\"{{ deploy_root }}\"\n",[115,21114,21115,21117],{"class":117,"line":16632},[115,21116,21109],{"class":125},[115,21118,21119],{"class":132},"\"{{ releases_dir }}\"\n",[115,21121,21122,21124],{"class":117,"line":16640},[115,21123,21109],{"class":125},[115,21125,21126],{"class":132},"\"{{ shared_dir }}\"\n",[115,21128,21129,21131],{"class":117,"line":16646},[115,21130,21109],{"class":125},[115,21132,21133],{"class":132},"\"{{ media_dir }}\"\n",[16,21135,21136],{},"Run it:",[106,21138,21140],{"className":108,"code":21139,"language":110,"meta":111,"style":111},"ansible-playbook -i inventory\u002Fproduction.ini playbooks\u002Fbootstrap.yml --ask-vault-pass\n",[20,21141,21142],{"__ignoreMap":111},[115,21143,21144,21147,21149,21152,21155],{"class":117,"line":118},[115,21145,21146],{"class":262},"ansible-playbook",[115,21148,14187],{"class":202},[115,21150,21151],{"class":132}," inventory\u002Fproduction.ini",[115,21153,21154],{"class":132}," playbooks\u002Fbootstrap.yml",[115,21156,21157],{"class":202}," --ask-vault-pass\n",[16,21159,8572],{},[106,21161,21163],{"className":108,"code":21162,"language":110,"meta":111,"style":111},"ssh deploy@app1.example.com 'ls -ld \u002Fsrv\u002Fmyapp \u002Fsrv\u002Fmyapp\u002Freleases \u002Fsrv\u002Fmyapp\u002Fshared \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia'\n",[20,21164,21165],{"__ignoreMap":111},[115,21166,21167,21169,21172],{"class":117,"line":118},[115,21168,14184],{"class":262},[115,21170,21171],{"class":132}," deploy@app1.example.com",[115,21173,21174],{"class":132}," 'ls -ld \u002Fsrv\u002Fmyapp \u002Fsrv\u002Fmyapp\u002Freleases \u002Fsrv\u002Fmyapp\u002Fshared \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia'\n",[16,21176,21177],{},"For firewall basics, allow only SSH, HTTP, and HTTPS if you use UFW. Keep SSH restrictions aligned with your access method.",[11,21179,21181],{"id":21180},"deploy-the-django-application-code","Deploy the Django application code",[16,21183,21184],{},[20,21185,21186],{},"playbooks\u002Fdeploy.yml",[106,21188,21190],{"className":2485,"code":21189,"language":2487,"meta":111,"style":111},"- hosts: web\n  become: true\n  vars:\n    release_name: \"{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}\"\n    release_path: \"{{ releases_dir }}\u002F{{ release_name }}\"\n  tasks:\n    - name: Create release directory\n      file:\n        path: \"{{ release_path }}\"\n        state: directory\n        owner: \"{{ app_user }}\"\n        group: \"{{ app_group }}\"\n        mode: \"0755\"\n\n    - name: Checkout application code\n      git:\n        repo: \"{{ repo_url }}\"\n        dest: \"{{ release_path }}\"\n        version: \"{{ deploy_version }}\"\n      become_user: \"{{ app_user }}\"\n\n    - name: Create virtualenv\n      command: \"{{ python_executable }} -m venv {{ venv_path }}\"\n      args:\n        creates: \"{{ venv_path }}\u002Fbin\u002Factivate\"\n\n    - name: Install Python dependencies\n      pip:\n        requirements: \"{{ release_path }}\u002Frequirements.txt\"\n        virtualenv: \"{{ venv_path }}\"\n\n    - name: Render environment file\n      template:\n        src: env.j2\n        dest: \"{{ env_file }}\"\n        owner: \"{{ app_user }}\"\n        group: \"{{ app_group }}\"\n        mode: \"0600\"\n",[20,21191,21192,21202,21210,21217,21227,21237,21243,21254,21260,21269,21277,21285,21293,21301,21305,21316,21323,21333,21342,21352,21361,21365,21376,21386,21393,21403,21407,21418,21425,21435,21445,21449,21460,21467,21477,21486,21494,21502],{"__ignoreMap":111},[115,21193,21194,21196,21198,21200],{"class":117,"line":118},[115,21195,20787],{"class":125},[115,21197,20790],{"class":2494},[115,21199,2513],{"class":125},[115,21201,20795],{"class":132},[115,21203,21204,21206,21208],{"class":117,"line":136},[115,21205,20800],{"class":2494},[115,21207,2513],{"class":125},[115,21209,20805],{"class":202},[115,21211,21212,21215],{"class":117,"line":149},[115,21213,21214],{"class":2494},"  vars",[115,21216,2498],{"class":125},[115,21218,21219,21222,21224],{"class":117,"line":162},[115,21220,21221],{"class":2494},"    release_name",[115,21223,2513],{"class":125},[115,21225,21226],{"class":132},"\"{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}\"\n",[115,21228,21229,21232,21234],{"class":117,"line":175},[115,21230,21231],{"class":2494},"    release_path",[115,21233,2513],{"class":125},[115,21235,21236],{"class":132},"\"{{ releases_dir }}\u002F{{ release_name }}\"\n",[115,21238,21239,21241],{"class":117,"line":350},[115,21240,20810],{"class":2494},[115,21242,2498],{"class":125},[115,21244,21245,21247,21249,21251],{"class":117,"line":365},[115,21246,20817],{"class":125},[115,21248,20820],{"class":2494},[115,21250,2513],{"class":125},[115,21252,21253],{"class":132},"Create release directory\n",[115,21255,21256,21258],{"class":117,"line":380},[115,21257,21049],{"class":2494},[115,21259,2498],{"class":125},[115,21261,21262,21264,21266],{"class":117,"line":487},[115,21263,21056],{"class":2494},[115,21265,2513],{"class":125},[115,21267,21268],{"class":132},"\"{{ release_path }}\"\n",[115,21270,21271,21273,21275],{"class":117,"line":2095},[115,21272,20907],{"class":2494},[115,21274,2513],{"class":125},[115,21276,21070],{"class":132},[115,21278,21279,21281,21283],{"class":117,"line":2104},[115,21280,21075],{"class":2494},[115,21282,2513],{"class":125},[115,21284,20991],{"class":132},[115,21286,21287,21289,21291],{"class":117,"line":2113},[115,21288,20996],{"class":2494},[115,21290,2513],{"class":125},[115,21292,20952],{"class":132},[115,21294,21295,21297,21299],{"class":117,"line":2122},[115,21296,21092],{"class":2494},[115,21298,2513],{"class":125},[115,21300,21097],{"class":132},[115,21302,21303],{"class":117,"line":2131},[115,21304,310],{"emptyLinePlaceholder":309},[115,21306,21307,21309,21311,21313],{"class":117,"line":2136},[115,21308,20817],{"class":125},[115,21310,20820],{"class":2494},[115,21312,2513],{"class":125},[115,21314,21315],{"class":132},"Checkout application code\n",[115,21317,21318,21321],{"class":117,"line":2142},[115,21319,21320],{"class":2494},"      git",[115,21322,2498],{"class":125},[115,21324,21325,21328,21330],{"class":117,"line":2273},[115,21326,21327],{"class":2494},"        repo",[115,21329,2513],{"class":125},[115,21331,21332],{"class":132},"\"{{ repo_url }}\"\n",[115,21334,21335,21338,21340],{"class":117,"line":2282},[115,21336,21337],{"class":2494},"        dest",[115,21339,2513],{"class":125},[115,21341,21268],{"class":132},[115,21343,21344,21347,21349],{"class":117,"line":2291},[115,21345,21346],{"class":2494},"        version",[115,21348,2513],{"class":125},[115,21350,21351],{"class":132},"\"{{ deploy_version }}\"\n",[115,21353,21354,21357,21359],{"class":117,"line":2299},[115,21355,21356],{"class":2494},"      become_user",[115,21358,2513],{"class":125},[115,21360,20991],{"class":132},[115,21362,21363],{"class":117,"line":2307},[115,21364,310],{"emptyLinePlaceholder":309},[115,21366,21367,21369,21371,21373],{"class":117,"line":2315},[115,21368,20817],{"class":125},[115,21370,20820],{"class":2494},[115,21372,2513],{"class":125},[115,21374,21375],{"class":132},"Create virtualenv\n",[115,21377,21378,21381,21383],{"class":117,"line":2320},[115,21379,21380],{"class":2494},"      command",[115,21382,2513],{"class":125},[115,21384,21385],{"class":132},"\"{{ python_executable }} -m venv {{ venv_path }}\"\n",[115,21387,21388,21391],{"class":117,"line":7083},[115,21389,21390],{"class":2494},"      args",[115,21392,2498],{"class":125},[115,21394,21395,21398,21400],{"class":117,"line":7090},[115,21396,21397],{"class":2494},"        creates",[115,21399,2513],{"class":125},[115,21401,21402],{"class":132},"\"{{ venv_path }}\u002Fbin\u002Factivate\"\n",[115,21404,21405],{"class":117,"line":7097},[115,21406,310],{"emptyLinePlaceholder":309},[115,21408,21409,21411,21413,21415],{"class":117,"line":7108},[115,21410,20817],{"class":125},[115,21412,20820],{"class":2494},[115,21414,2513],{"class":125},[115,21416,21417],{"class":132},"Install Python dependencies\n",[115,21419,21420,21423],{"class":117,"line":7113},[115,21421,21422],{"class":2494},"      pip",[115,21424,2498],{"class":125},[115,21426,21427,21430,21432],{"class":117,"line":16535},[115,21428,21429],{"class":2494},"        requirements",[115,21431,2513],{"class":125},[115,21433,21434],{"class":132},"\"{{ release_path }}\u002Frequirements.txt\"\n",[115,21436,21437,21440,21442],{"class":117,"line":16544},[115,21438,21439],{"class":2494},"        virtualenv",[115,21441,2513],{"class":125},[115,21443,21444],{"class":132},"\"{{ venv_path }}\"\n",[115,21446,21447],{"class":117,"line":16549},[115,21448,310],{"emptyLinePlaceholder":309},[115,21450,21451,21453,21455,21457],{"class":117,"line":16555},[115,21452,20817],{"class":125},[115,21454,20820],{"class":2494},[115,21456,2513],{"class":125},[115,21458,21459],{"class":132},"Render environment file\n",[115,21461,21462,21465],{"class":117,"line":16564},[115,21463,21464],{"class":2494},"      template",[115,21466,2498],{"class":125},[115,21468,21469,21472,21474],{"class":117,"line":16573},[115,21470,21471],{"class":2494},"        src",[115,21473,2513],{"class":125},[115,21475,21476],{"class":132},"env.j2\n",[115,21478,21479,21481,21483],{"class":117,"line":16582},[115,21480,21337],{"class":2494},[115,21482,2513],{"class":125},[115,21484,21485],{"class":132},"\"{{ env_file }}\"\n",[115,21487,21488,21490,21492],{"class":117,"line":16587},[115,21489,21075],{"class":2494},[115,21491,2513],{"class":125},[115,21493,20991],{"class":132},[115,21495,21496,21498,21500],{"class":117,"line":16596},[115,21497,20996],{"class":2494},[115,21499,2513],{"class":125},[115,21501,20952],{"class":132},[115,21503,21504,21506,21508],{"class":117,"line":16609},[115,21505,21092],{"class":2494},[115,21507,2513],{"class":125},[115,21509,21510],{"class":132},"\"0600\"\n",[16,21512,21513,21514,21517],{},"Prefer preloading the Git host key in ",[20,21515,21516],{},"known_hosts"," instead of relying on trust-on-first-use behavior in production.",[16,21519,21520],{},[20,21521,21522],{},"templates\u002Fenv.j2",[106,21524,21528],{"className":21525,"code":21526,"language":21527,"meta":111,"style":111},"language-dotenv shiki shiki-themes github-light github-dark","DEBUG=False\nSECRET_KEY=\"{{ django_secret_key }}\"\nALLOWED_HOSTS=\"{{ app_domain }}\"\nCSRF_TRUSTED_ORIGINS=\"{{ csrf_trusted_origins | join(',') }}\"\n\nDATABASE_NAME=\"{{ db_name }}\"\nDATABASE_USER=\"{{ db_user }}\"\nDATABASE_PASSWORD=\"{{ db_password }}\"\nDATABASE_HOST=\"{{ db_host }}\"\nDATABASE_PORT=\"{{ db_port }}\"\n","dotenv",[20,21529,21530,21535,21540,21545,21550,21554,21559,21564,21569,21574],{"__ignoreMap":111},[115,21531,21532],{"class":117,"line":118},[115,21533,21534],{},"DEBUG=False\n",[115,21536,21537],{"class":117,"line":136},[115,21538,21539],{},"SECRET_KEY=\"{{ django_secret_key }}\"\n",[115,21541,21542],{"class":117,"line":149},[115,21543,21544],{},"ALLOWED_HOSTS=\"{{ app_domain }}\"\n",[115,21546,21547],{"class":117,"line":162},[115,21548,21549],{},"CSRF_TRUSTED_ORIGINS=\"{{ csrf_trusted_origins | join(',') }}\"\n",[115,21551,21552],{"class":117,"line":175},[115,21553,310],{"emptyLinePlaceholder":309},[115,21555,21556],{"class":117,"line":350},[115,21557,21558],{},"DATABASE_NAME=\"{{ db_name }}\"\n",[115,21560,21561],{"class":117,"line":365},[115,21562,21563],{},"DATABASE_USER=\"{{ db_user }}\"\n",[115,21565,21566],{"class":117,"line":380},[115,21567,21568],{},"DATABASE_PASSWORD=\"{{ db_password }}\"\n",[115,21570,21571],{"class":117,"line":487},[115,21572,21573],{},"DATABASE_HOST=\"{{ db_host }}\"\n",[115,21575,21576],{"class":117,"line":2095},[115,21577,21578],{},"DATABASE_PORT=\"{{ db_port }}\"\n",[16,21580,21581,21582,211],{},"For reproducibility, deploy a tag or commit SHA instead of always using ",[20,21583,21584],{},"main",[11,21586,21588],{"id":21587},"run-django-production-tasks-safely","Run Django production tasks safely",[16,21590,21591,21592,21594],{},"Run these tasks before switching traffic to the new release where possible. If you switch the symlink first, a failed migration or check can leave ",[20,21593,13654],{}," pointing at a bad release.",[16,21596,21597,21598,21600],{},"Add these tasks to ",[20,21599,21186],{}," after the environment file is rendered:",[106,21602,21604],{"className":2485,"code":21603,"language":2487,"meta":111,"style":111},"    - name: Run database migrations\n      shell: \"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py migrate --noinput\"\n      args:\n        chdir: \"{{ release_path }}\"\n      become_user: \"{{ app_user }}\"\n      environment:\n        DJANGO_SETTINGS_MODULE: \"config.settings\"\n\n    - name: Collect static files\n      shell: \"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py collectstatic --noinput\"\n      args:\n        chdir: \"{{ release_path }}\"\n      become_user: \"{{ app_user }}\"\n      environment:\n        DJANGO_SETTINGS_MODULE: \"config.settings\"\n\n    - name: Run Django deployment checks\n      shell: \"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py check --deploy\"\n      args:\n        chdir: \"{{ release_path }}\"\n      become_user: \"{{ app_user }}\"\n      environment:\n        DJANGO_SETTINGS_MODULE: \"config.settings\"\n\n    - name: Point current symlink to release\n      file:\n        src: \"{{ release_path }}\"\n        dest: \"{{ current_dir }}\"\n        state: link\n        force: true\n",[20,21605,21606,21617,21627,21633,21642,21650,21657,21667,21671,21682,21691,21697,21705,21713,21719,21727,21731,21742,21751,21757,21765,21773,21779,21787,21791,21802,21808,21816,21824,21833],{"__ignoreMap":111},[115,21607,21608,21610,21612,21614],{"class":117,"line":118},[115,21609,20817],{"class":125},[115,21611,20820],{"class":2494},[115,21613,2513],{"class":125},[115,21615,21616],{"class":132},"Run database migrations\n",[115,21618,21619,21622,21624],{"class":117,"line":136},[115,21620,21621],{"class":2494},"      shell",[115,21623,2513],{"class":125},[115,21625,21626],{"class":132},"\"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py migrate --noinput\"\n",[115,21628,21629,21631],{"class":117,"line":149},[115,21630,21390],{"class":2494},[115,21632,2498],{"class":125},[115,21634,21635,21638,21640],{"class":117,"line":162},[115,21636,21637],{"class":2494},"        chdir",[115,21639,2513],{"class":125},[115,21641,21268],{"class":132},[115,21643,21644,21646,21648],{"class":117,"line":175},[115,21645,21356],{"class":2494},[115,21647,2513],{"class":125},[115,21649,20991],{"class":132},[115,21651,21652,21655],{"class":117,"line":350},[115,21653,21654],{"class":2494},"      environment",[115,21656,2498],{"class":125},[115,21658,21659,21662,21664],{"class":117,"line":365},[115,21660,21661],{"class":2494},"        DJANGO_SETTINGS_MODULE",[115,21663,2513],{"class":125},[115,21665,21666],{"class":132},"\"config.settings\"\n",[115,21668,21669],{"class":117,"line":380},[115,21670,310],{"emptyLinePlaceholder":309},[115,21672,21673,21675,21677,21679],{"class":117,"line":487},[115,21674,20817],{"class":125},[115,21676,20820],{"class":2494},[115,21678,2513],{"class":125},[115,21680,21681],{"class":132},"Collect static files\n",[115,21683,21684,21686,21688],{"class":117,"line":2095},[115,21685,21621],{"class":2494},[115,21687,2513],{"class":125},[115,21689,21690],{"class":132},"\"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py collectstatic --noinput\"\n",[115,21692,21693,21695],{"class":117,"line":2104},[115,21694,21390],{"class":2494},[115,21696,2498],{"class":125},[115,21698,21699,21701,21703],{"class":117,"line":2113},[115,21700,21637],{"class":2494},[115,21702,2513],{"class":125},[115,21704,21268],{"class":132},[115,21706,21707,21709,21711],{"class":117,"line":2122},[115,21708,21356],{"class":2494},[115,21710,2513],{"class":125},[115,21712,20991],{"class":132},[115,21714,21715,21717],{"class":117,"line":2131},[115,21716,21654],{"class":2494},[115,21718,2498],{"class":125},[115,21720,21721,21723,21725],{"class":117,"line":2136},[115,21722,21661],{"class":2494},[115,21724,2513],{"class":125},[115,21726,21666],{"class":132},[115,21728,21729],{"class":117,"line":2142},[115,21730,310],{"emptyLinePlaceholder":309},[115,21732,21733,21735,21737,21739],{"class":117,"line":2273},[115,21734,20817],{"class":125},[115,21736,20820],{"class":2494},[115,21738,2513],{"class":125},[115,21740,21741],{"class":132},"Run Django deployment checks\n",[115,21743,21744,21746,21748],{"class":117,"line":2282},[115,21745,21621],{"class":2494},[115,21747,2513],{"class":125},[115,21749,21750],{"class":132},"\"set -a && . {{ env_file }} && set +a && {{ venv_path }}\u002Fbin\u002Fpython manage.py check --deploy\"\n",[115,21752,21753,21755],{"class":117,"line":2291},[115,21754,21390],{"class":2494},[115,21756,2498],{"class":125},[115,21758,21759,21761,21763],{"class":117,"line":2299},[115,21760,21637],{"class":2494},[115,21762,2513],{"class":125},[115,21764,21268],{"class":132},[115,21766,21767,21769,21771],{"class":117,"line":2307},[115,21768,21356],{"class":2494},[115,21770,2513],{"class":125},[115,21772,20991],{"class":132},[115,21774,21775,21777],{"class":117,"line":2315},[115,21776,21654],{"class":2494},[115,21778,2498],{"class":125},[115,21780,21781,21783,21785],{"class":117,"line":2320},[115,21782,21661],{"class":2494},[115,21784,2513],{"class":125},[115,21786,21666],{"class":132},[115,21788,21789],{"class":117,"line":7083},[115,21790,310],{"emptyLinePlaceholder":309},[115,21792,21793,21795,21797,21799],{"class":117,"line":7090},[115,21794,20817],{"class":125},[115,21796,20820],{"class":2494},[115,21798,2513],{"class":125},[115,21800,21801],{"class":132},"Point current symlink to release\n",[115,21803,21804,21806],{"class":117,"line":7097},[115,21805,21049],{"class":2494},[115,21807,2498],{"class":125},[115,21809,21810,21812,21814],{"class":117,"line":7108},[115,21811,21471],{"class":2494},[115,21813,2513],{"class":125},[115,21815,21268],{"class":132},[115,21817,21818,21820,21822],{"class":117,"line":7113},[115,21819,21337],{"class":2494},[115,21821,2513],{"class":125},[115,21823,20635],{"class":132},[115,21825,21826,21828,21830],{"class":117,"line":16535},[115,21827,20907],{"class":2494},[115,21829,2513],{"class":125},[115,21831,21832],{"class":132},"link\n",[115,21834,21835,21838,21840],{"class":117,"line":16544},[115,21836,21837],{"class":2494},"        force",[115,21839,2513],{"class":125},[115,21841,20805],{"class":202},[16,21843,21844],{},"Verification after this stage:",[106,21846,21848],{"className":108,"code":21847,"language":110,"meta":111,"style":111},"ansible web -i inventory\u002Fproduction.ini -b -a \"sudo -u myapp \u002Fsrv\u002Fmyapp\u002Fshared\u002Fvenv\u002Fbin\u002Fpython \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fmanage.py showmigrations\" --ask-vault-pass\n",[20,21849,21850],{"__ignoreMap":111},[115,21851,21852,21855,21857,21859,21861,21864,21866,21869],{"class":117,"line":118},[115,21853,21854],{"class":262},"ansible",[115,21856,3304],{"class":132},[115,21858,14187],{"class":202},[115,21860,21151],{"class":132},[115,21862,21863],{"class":202}," -b",[115,21865,8584],{"class":202},[115,21867,21868],{"class":132}," \"sudo -u myapp \u002Fsrv\u002Fmyapp\u002Fshared\u002Fvenv\u002Fbin\u002Fpython \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fmanage.py showmigrations\"",[115,21870,21157],{"class":202},[16,21872,21873],{},"If migrations are destructive or hard to reverse, take a database backup before deploy. That matters more than the automation tool.",[11,21875,14116],{"id":21876},"configure-gunicorn-with-systemd",[16,21878,21879],{},[20,21880,21881],{},"templates\u002Fgunicorn.service.j2",[106,21883,21885],{"className":2026,"code":21884,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for {{ app_name }}\nAfter=network.target\n\n[Service]\nUser={{ app_user }}\nGroup={{ app_group }}\nWorkingDirectory={{ current_dir }}\nEnvironmentFile={{ env_file }}\nExecStart={{ venv_path }}\u002Fbin\u002Fgunicorn --workers 3 --bind {{ gunicorn_bind }} {{ django_wsgi_module }}\nExecReload=\u002Fbin\u002Fkill -HUP $MAINPID\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n",[20,21886,21887,21891,21898,21904,21908,21912,21919,21926,21933,21940,21947,21955,21961,21967,21971,21975],{"__ignoreMap":111},[115,21888,21889],{"class":117,"line":118},[115,21890,2035],{"class":262},[115,21892,21893,21895],{"class":117,"line":136},[115,21894,2040],{"class":121},[115,21896,21897],{"class":125},"=Gunicorn for {{ app_name }}\n",[115,21899,21900,21902],{"class":117,"line":149},[115,21901,2048],{"class":121},[115,21903,2051],{"class":125},[115,21905,21906],{"class":117,"line":162},[115,21907,310],{"emptyLinePlaceholder":309},[115,21909,21910],{"class":117,"line":175},[115,21911,2060],{"class":262},[115,21913,21914,21916],{"class":117,"line":350},[115,21915,2065],{"class":121},[115,21917,21918],{"class":125},"={{ app_user }}\n",[115,21920,21921,21923],{"class":117,"line":365},[115,21922,2073],{"class":121},[115,21924,21925],{"class":125},"={{ app_group }}\n",[115,21927,21928,21930],{"class":117,"line":380},[115,21929,2081],{"class":121},[115,21931,21932],{"class":125},"={{ current_dir }}\n",[115,21934,21935,21937],{"class":117,"line":487},[115,21936,2089],{"class":121},[115,21938,21939],{"class":125},"={{ env_file }}\n",[115,21941,21942,21944],{"class":117,"line":2095},[115,21943,2107],{"class":121},[115,21945,21946],{"class":125},"={{ venv_path }}\u002Fbin\u002Fgunicorn --workers 3 --bind {{ gunicorn_bind }} {{ django_wsgi_module }}\n",[115,21948,21949,21952],{"class":117,"line":2104},[115,21950,21951],{"class":121},"ExecReload",[115,21953,21954],{"class":125},"=\u002Fbin\u002Fkill -HUP $MAINPID\n",[115,21956,21957,21959],{"class":117,"line":2113},[115,21958,2116],{"class":121},[115,21960,4932],{"class":125},[115,21962,21963,21965],{"class":117,"line":2122},[115,21964,2125],{"class":121},[115,21966,2128],{"class":125},[115,21968,21969],{"class":117,"line":2131},[115,21970,310],{"emptyLinePlaceholder":309},[115,21972,21973],{"class":117,"line":2136},[115,21974,2139],{"class":262},[115,21976,21977,21979],{"class":117,"line":2142},[115,21978,2145],{"class":121},[115,21980,2148],{"class":125},[16,21982,21983],{},"Add these tasks:",[106,21985,21987],{"className":2485,"code":21986,"language":2487,"meta":111,"style":111},"    - name: Install Gunicorn systemd unit\n      template:\n        src: gunicorn.service.j2\n        dest: \"\u002Fetc\u002Fsystemd\u002Fsystem\u002F{{ app_name }}-gunicorn.service\"\n        mode: \"0644\"\n      notify:\n        - reload systemd\n        - restart gunicorn\n\n  handlers:\n    - name: reload systemd\n      systemd:\n        daemon_reload: true\n\n    - name: restart gunicorn\n      systemd:\n        name: \"{{ app_name }}-gunicorn\"\n        state: restarted\n        enabled: true\n",[20,21988,21989,22000,22006,22015,22024,22033,22040,22047,22054,22058,22065,22075,22082,22091,22095,22105,22111,22120,22129],{"__ignoreMap":111},[115,21990,21991,21993,21995,21997],{"class":117,"line":118},[115,21992,20817],{"class":125},[115,21994,20820],{"class":2494},[115,21996,2513],{"class":125},[115,21998,21999],{"class":132},"Install Gunicorn systemd unit\n",[115,22001,22002,22004],{"class":117,"line":136},[115,22003,21464],{"class":2494},[115,22005,2498],{"class":125},[115,22007,22008,22010,22012],{"class":117,"line":149},[115,22009,21471],{"class":2494},[115,22011,2513],{"class":125},[115,22013,22014],{"class":132},"gunicorn.service.j2\n",[115,22016,22017,22019,22021],{"class":117,"line":162},[115,22018,21337],{"class":2494},[115,22020,2513],{"class":125},[115,22022,22023],{"class":132},"\"\u002Fetc\u002Fsystemd\u002Fsystem\u002F{{ app_name }}-gunicorn.service\"\n",[115,22025,22026,22028,22030],{"class":117,"line":175},[115,22027,21092],{"class":2494},[115,22029,2513],{"class":125},[115,22031,22032],{"class":132},"\"0644\"\n",[115,22034,22035,22038],{"class":117,"line":350},[115,22036,22037],{"class":2494},"      notify",[115,22039,2498],{"class":125},[115,22041,22042,22044],{"class":117,"line":365},[115,22043,21109],{"class":125},[115,22045,22046],{"class":132},"reload systemd\n",[115,22048,22049,22051],{"class":117,"line":380},[115,22050,21109],{"class":125},[115,22052,22053],{"class":132},"restart gunicorn\n",[115,22055,22056],{"class":117,"line":487},[115,22057,310],{"emptyLinePlaceholder":309},[115,22059,22060,22063],{"class":117,"line":2095},[115,22061,22062],{"class":2494},"  handlers",[115,22064,2498],{"class":125},[115,22066,22067,22069,22071,22073],{"class":117,"line":2104},[115,22068,20817],{"class":125},[115,22070,20820],{"class":2494},[115,22072,2513],{"class":125},[115,22074,22046],{"class":132},[115,22076,22077,22080],{"class":117,"line":2113},[115,22078,22079],{"class":2494},"      systemd",[115,22081,2498],{"class":125},[115,22083,22084,22087,22089],{"class":117,"line":2122},[115,22085,22086],{"class":2494},"        daemon_reload",[115,22088,2513],{"class":125},[115,22090,20805],{"class":202},[115,22092,22093],{"class":117,"line":2131},[115,22094,310],{"emptyLinePlaceholder":309},[115,22096,22097,22099,22101,22103],{"class":117,"line":2136},[115,22098,20817],{"class":125},[115,22100,20820],{"class":2494},[115,22102,2513],{"class":125},[115,22104,22053],{"class":132},[115,22106,22107,22109],{"class":117,"line":2142},[115,22108,22079],{"class":2494},[115,22110,2498],{"class":125},[115,22112,22113,22115,22117],{"class":117,"line":2273},[115,22114,20837],{"class":2494},[115,22116,2513],{"class":125},[115,22118,22119],{"class":132},"\"{{ app_name }}-gunicorn\"\n",[115,22121,22122,22124,22126],{"class":117,"line":2282},[115,22123,20907],{"class":2494},[115,22125,2513],{"class":125},[115,22127,22128],{"class":132},"restarted\n",[115,22130,22131,22134,22136],{"class":117,"line":2291},[115,22132,22133],{"class":2494},"        enabled",[115,22135,2513],{"class":125},[115,22137,20805],{"class":202},[16,22139,8572],{},[106,22141,22143],{"className":108,"code":22142,"language":110,"meta":111,"style":111},"ssh deploy@app1.example.com 'systemctl status myapp-gunicorn --no-pager'\nssh deploy@app1.example.com 'journalctl -u myapp-gunicorn -n 50 --no-pager'\ncurl http:\u002F\u002F127.0.0.1:8000\u002F\n",[20,22144,22145,22154,22163],{"__ignoreMap":111},[115,22146,22147,22149,22151],{"class":117,"line":118},[115,22148,14184],{"class":262},[115,22150,21171],{"class":132},[115,22152,22153],{"class":132}," 'systemctl status myapp-gunicorn --no-pager'\n",[115,22155,22156,22158,22160],{"class":117,"line":136},[115,22157,14184],{"class":262},[115,22159,21171],{"class":132},[115,22161,22162],{"class":132}," 'journalctl -u myapp-gunicorn -n 50 --no-pager'\n",[115,22164,22165,22167],{"class":117,"line":149},[115,22166,2764],{"class":262},[115,22168,3950],{"class":132},[11,22170,22172],{"id":22171},"configure-nginx-as-the-reverse-proxy","Configure Nginx as the reverse proxy",[16,22174,22175],{},"Use HTTP only for initial local validation, but treat TLS as part of the production setup. At minimum, terminate HTTPS in Nginx or Caddy before serving public traffic.",[16,22177,22178],{},[20,22179,22180],{},"templates\u002Fnginx-site.conf.j2",[106,22182,22184],{"className":2154,"code":22183,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name {{ app_domain }};\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name {{ app_domain }};\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002F{{ app_domain }}\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002F{{ app_domain }}\u002Fprivkey.pem;\n\n    location \u002Fstatic\u002F {\n        alias {{ current_dir }}\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias {{ media_dir }}\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F{{ gunicorn_bind }};\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto https;\n        proxy_redirect off;\n    }\n}\n",[20,22185,22186,22192,22200,22207,22215,22219,22223,22229,22237,22243,22247,22254,22261,22265,22273,22280,22284,22288,22296,22303,22307,22311,22319,22326,22332,22338,22344,22351,22359,22363],{"__ignoreMap":111},[115,22187,22188,22190],{"class":117,"line":118},[115,22189,2163],{"class":121},[115,22191,2166],{"class":125},[115,22193,22194,22196,22198],{"class":117,"line":136},[115,22195,2171],{"class":121},[115,22197,3808],{"class":202},[115,22199,3811],{"class":125},[115,22201,22202,22204],{"class":117,"line":149},[115,22203,2182],{"class":121},[115,22205,22206],{"class":125},"{{ app_domain }};\n",[115,22208,22209,22211,22213],{"class":117,"line":162},[115,22210,3822],{"class":121},[115,22212,3825],{"class":202},[115,22214,3828],{"class":125},[115,22216,22217],{"class":117,"line":175},[115,22218,2323],{"class":125},[115,22220,22221],{"class":117,"line":350},[115,22222,310],{"emptyLinePlaceholder":309},[115,22224,22225,22227],{"class":117,"line":365},[115,22226,2163],{"class":121},[115,22228,2166],{"class":125},[115,22230,22231,22233,22235],{"class":117,"line":380},[115,22232,2171],{"class":121},[115,22234,2174],{"class":202},[115,22236,2177],{"class":125},[115,22238,22239,22241],{"class":117,"line":487},[115,22240,2182],{"class":121},[115,22242,22206],{"class":125},[115,22244,22245],{"class":117,"line":2095},[115,22246,310],{"emptyLinePlaceholder":309},[115,22248,22249,22251],{"class":117,"line":2104},[115,22250,2194],{"class":121},[115,22252,22253],{"class":125},"\u002Fetc\u002Fletsencrypt\u002Flive\u002F{{ app_domain }}\u002Ffullchain.pem;\n",[115,22255,22256,22258],{"class":117,"line":2113},[115,22257,2202],{"class":121},[115,22259,22260],{"class":125},"\u002Fetc\u002Fletsencrypt\u002Flive\u002F{{ app_domain }}\u002Fprivkey.pem;\n",[115,22262,22263],{"class":117,"line":2122},[115,22264,310],{"emptyLinePlaceholder":309},[115,22266,22267,22269,22271],{"class":117,"line":2131},[115,22268,2214],{"class":121},[115,22270,2217],{"class":262},[115,22272,2220],{"class":125},[115,22274,22275,22277],{"class":117,"line":2136},[115,22276,2225],{"class":121},[115,22278,22279],{"class":125},"{{ current_dir }}\u002Fstaticfiles\u002F;\n",[115,22281,22282],{"class":117,"line":2142},[115,22283,2233],{"class":125},[115,22285,22286],{"class":117,"line":2273},[115,22287,310],{"emptyLinePlaceholder":309},[115,22289,22290,22292,22294],{"class":117,"line":2282},[115,22291,2214],{"class":121},[115,22293,2244],{"class":262},[115,22295,2220],{"class":125},[115,22297,22298,22300],{"class":117,"line":2291},[115,22299,2225],{"class":121},[115,22301,22302],{"class":125},"{{ media_dir }}\u002F;\n",[115,22304,22305],{"class":117,"line":2299},[115,22306,2233],{"class":125},[115,22308,22309],{"class":117,"line":2307},[115,22310,310],{"emptyLinePlaceholder":309},[115,22312,22313,22315,22317],{"class":117,"line":2315},[115,22314,2214],{"class":121},[115,22316,2268],{"class":262},[115,22318,2220],{"class":125},[115,22320,22321,22323],{"class":117,"line":2320},[115,22322,2276],{"class":121},[115,22324,22325],{"class":125},"http:\u002F\u002F{{ gunicorn_bind }};\n",[115,22327,22328,22330],{"class":117,"line":7083},[115,22329,2285],{"class":121},[115,22331,2288],{"class":125},[115,22333,22334,22336],{"class":117,"line":7090},[115,22335,2285],{"class":121},[115,22337,3767],{"class":125},[115,22339,22340,22342],{"class":117,"line":7097},[115,22341,2285],{"class":121},[115,22343,2312],{"class":125},[115,22345,22346,22348],{"class":117,"line":7108},[115,22347,2285],{"class":121},[115,22349,22350],{"class":125},"X-Forwarded-Proto https;\n",[115,22352,22353,22355,22357],{"class":117,"line":7113},[115,22354,7100],{"class":121},[115,22356,7103],{"class":202},[115,22358,3811],{"class":125},[115,22360,22361],{"class":117,"line":16535},[115,22362,2233],{"class":125},[115,22364,22365],{"class":117,"line":16544},[115,22366,2323],{"class":125},[16,22368,22369],{},"Tasks:",[106,22371,22373],{"className":2485,"code":22372,"language":2487,"meta":111,"style":111},"    - name: Install Nginx site config\n      template:\n        src: nginx-site.conf.j2\n        dest: \"\u002Fetc\u002Fnginx\u002Fsites-available\u002F{{ app_name }}.conf\"\n        mode: \"0644\"\n\n    - name: Enable Nginx site\n      file:\n        src: \"\u002Fetc\u002Fnginx\u002Fsites-available\u002F{{ app_name }}.conf\"\n        dest: \"\u002Fetc\u002Fnginx\u002Fsites-enabled\u002F{{ app_name }}.conf\"\n        state: link\n        force: true\n\n    - name: Disable default Nginx site\n      file:\n        path: \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\n        state: absent\n\n    - name: Validate Nginx config\n      command: nginx -t\n\n    - name: Reload Nginx\n      systemd:\n        name: nginx\n        state: reloaded\n        enabled: true\n",[20,22374,22375,22386,22392,22401,22410,22418,22422,22433,22439,22447,22456,22464,22472,22476,22487,22493,22502,22511,22515,22526,22535,22539,22550,22556,22564,22573],{"__ignoreMap":111},[115,22376,22377,22379,22381,22383],{"class":117,"line":118},[115,22378,20817],{"class":125},[115,22380,20820],{"class":2494},[115,22382,2513],{"class":125},[115,22384,22385],{"class":132},"Install Nginx site config\n",[115,22387,22388,22390],{"class":117,"line":136},[115,22389,21464],{"class":2494},[115,22391,2498],{"class":125},[115,22393,22394,22396,22398],{"class":117,"line":149},[115,22395,21471],{"class":2494},[115,22397,2513],{"class":125},[115,22399,22400],{"class":132},"nginx-site.conf.j2\n",[115,22402,22403,22405,22407],{"class":117,"line":162},[115,22404,21337],{"class":2494},[115,22406,2513],{"class":125},[115,22408,22409],{"class":132},"\"\u002Fetc\u002Fnginx\u002Fsites-available\u002F{{ app_name }}.conf\"\n",[115,22411,22412,22414,22416],{"class":117,"line":175},[115,22413,21092],{"class":2494},[115,22415,2513],{"class":125},[115,22417,22032],{"class":132},[115,22419,22420],{"class":117,"line":350},[115,22421,310],{"emptyLinePlaceholder":309},[115,22423,22424,22426,22428,22430],{"class":117,"line":365},[115,22425,20817],{"class":125},[115,22427,20820],{"class":2494},[115,22429,2513],{"class":125},[115,22431,22432],{"class":132},"Enable Nginx site\n",[115,22434,22435,22437],{"class":117,"line":380},[115,22436,21049],{"class":2494},[115,22438,2498],{"class":125},[115,22440,22441,22443,22445],{"class":117,"line":487},[115,22442,21471],{"class":2494},[115,22444,2513],{"class":125},[115,22446,22409],{"class":132},[115,22448,22449,22451,22453],{"class":117,"line":2095},[115,22450,21337],{"class":2494},[115,22452,2513],{"class":125},[115,22454,22455],{"class":132},"\"\u002Fetc\u002Fnginx\u002Fsites-enabled\u002F{{ app_name }}.conf\"\n",[115,22457,22458,22460,22462],{"class":117,"line":2104},[115,22459,20907],{"class":2494},[115,22461,2513],{"class":125},[115,22463,21832],{"class":132},[115,22465,22466,22468,22470],{"class":117,"line":2113},[115,22467,21837],{"class":2494},[115,22469,2513],{"class":125},[115,22471,20805],{"class":202},[115,22473,22474],{"class":117,"line":2122},[115,22475,310],{"emptyLinePlaceholder":309},[115,22477,22478,22480,22482,22484],{"class":117,"line":2131},[115,22479,20817],{"class":125},[115,22481,20820],{"class":2494},[115,22483,2513],{"class":125},[115,22485,22486],{"class":132},"Disable default Nginx site\n",[115,22488,22489,22491],{"class":117,"line":2136},[115,22490,21049],{"class":2494},[115,22492,2498],{"class":125},[115,22494,22495,22497,22499],{"class":117,"line":2142},[115,22496,21056],{"class":2494},[115,22498,2513],{"class":125},[115,22500,22501],{"class":132},"\u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\n",[115,22503,22504,22506,22508],{"class":117,"line":2273},[115,22505,20907],{"class":2494},[115,22507,2513],{"class":125},[115,22509,22510],{"class":132},"absent\n",[115,22512,22513],{"class":117,"line":2282},[115,22514,310],{"emptyLinePlaceholder":309},[115,22516,22517,22519,22521,22523],{"class":117,"line":2291},[115,22518,20817],{"class":125},[115,22520,20820],{"class":2494},[115,22522,2513],{"class":125},[115,22524,22525],{"class":132},"Validate Nginx config\n",[115,22527,22528,22530,22532],{"class":117,"line":2299},[115,22529,21380],{"class":2494},[115,22531,2513],{"class":125},[115,22533,22534],{"class":132},"nginx -t\n",[115,22536,22537],{"class":117,"line":2307},[115,22538,310],{"emptyLinePlaceholder":309},[115,22540,22541,22543,22545,22547],{"class":117,"line":2315},[115,22542,20817],{"class":125},[115,22544,20820],{"class":2494},[115,22546,2513],{"class":125},[115,22548,22549],{"class":132},"Reload Nginx\n",[115,22551,22552,22554],{"class":117,"line":2320},[115,22553,22079],{"class":2494},[115,22555,2498],{"class":125},[115,22557,22558,22560,22562],{"class":117,"line":7083},[115,22559,20837],{"class":2494},[115,22561,2513],{"class":125},[115,22563,20895],{"class":132},[115,22565,22566,22568,22570],{"class":117,"line":7090},[115,22567,20907],{"class":2494},[115,22569,2513],{"class":125},[115,22571,22572],{"class":132},"reloaded\n",[115,22574,22575,22577,22579],{"class":117,"line":7097},[115,22576,22133],{"class":2494},[115,22578,2513],{"class":125},[115,22580,20805],{"class":202},[16,22582,8572],{},[106,22584,22586],{"className":108,"code":22585,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fapp1.example.com\ncurl -Ik https:\u002F\u002Fapp1.example.com\nssh deploy@app1.example.com 'systemctl status nginx --no-pager'\n",[20,22587,22588,22597,22606],{"__ignoreMap":111},[115,22589,22590,22592,22594],{"class":117,"line":118},[115,22591,2764],{"class":262},[115,22593,2767],{"class":202},[115,22595,22596],{"class":132}," http:\u002F\u002Fapp1.example.com\n",[115,22598,22599,22601,22603],{"class":117,"line":136},[115,22600,2764],{"class":262},[115,22602,7659],{"class":202},[115,22604,22605],{"class":132}," https:\u002F\u002Fapp1.example.com\n",[115,22607,22608,22610,22612],{"class":117,"line":149},[115,22609,14184],{"class":262},[115,22611,21171],{"class":132},[115,22613,22614],{"class":132}," 'systemctl status nginx --no-pager'\n",[16,22616,22617,22618,22620,22621,22623,22624,1153,22627,22630,22631,22633],{},"If Django is behind a TLS-terminating proxy, configure ",[20,22619,13869],{},", set ",[20,22622,2725],{},", and enable secure cookies as appropriate. In many deployments that also means setting ",[20,22625,22626],{},"SESSION_COOKIE_SECURE = True",[20,22628,22629],{},"CSRF_COOKIE_SECURE = True",", and reviewing whether ",[20,22632,2407],{}," should be enabled in Django.",[11,22635,22637],{"id":22636},"build-a-repeatable-release-workflow-with-ansible","Build a repeatable release workflow with Ansible",[16,22639,22640],{},"Keep bootstrap and deploy separate. Bootstrap is one-time or infrequent. Deploy runs every release.",[16,22642,22643],{},"A practical release sequence is:",[1173,22645,22646,22649,22652,22655,22658,22661,22665,22671,22674],{},[66,22647,22648],{},"create a new release directory",[66,22650,22651],{},"checkout the target Git ref",[66,22653,22654],{},"install dependencies into the shared virtualenv",[66,22656,22657],{},"render the environment file",[66,22659,22660],{},"run migrations and collect static against the new release",[66,22662,7902,22663],{},[20,22664,20349],{},[66,22666,22667,22668,22670],{},"switch ",[20,22669,13654],{}," to the new release",[66,22672,22673],{},"restart Gunicorn",[66,22675,22676],{},"reload Nginx after config validation",[16,22678,22679],{},"Re-running the playbook should not break the host or restart services unnecessarily.",[52,22681,22683],{"id":22682},"when-to-automate-this-further","When to automate this further",[16,22685,22686],{},"Once you manage more than one app or environment, this playbook structure is a good candidate for reusable roles and templates. The first pieces worth standardizing are environment file rendering, systemd unit templates, Nginx site templates, pre-deploy checks, release retention, and symlink switching.",[11,22688,22690],{"id":22689},"verification-after-deployment","Verification after deployment",[16,22692,22693],{},"Check the release from outside and on the server:",[106,22695,22697],{"className":108,"code":22696,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fapp1.example.com\ncurl -Ik https:\u002F\u002Fapp1.example.com\ncurl -Ik https:\u002F\u002Fapp1.example.com\u002Fadmin\u002Flogin\u002F\nssh deploy@app1.example.com 'systemctl is-active myapp-gunicorn nginx'\nssh deploy@app1.example.com 'journalctl -u myapp-gunicorn -n 50 --no-pager'\nssh deploy@app1.example.com 'readlink -f \u002Fsrv\u002Fmyapp\u002Fcurrent'\n",[20,22698,22699,22707,22715,22724,22733,22741],{"__ignoreMap":111},[115,22700,22701,22703,22705],{"class":117,"line":118},[115,22702,2764],{"class":262},[115,22704,2767],{"class":202},[115,22706,22596],{"class":132},[115,22708,22709,22711,22713],{"class":117,"line":136},[115,22710,2764],{"class":262},[115,22712,7659],{"class":202},[115,22714,22605],{"class":132},[115,22716,22717,22719,22721],{"class":117,"line":149},[115,22718,2764],{"class":262},[115,22720,7659],{"class":202},[115,22722,22723],{"class":132}," https:\u002F\u002Fapp1.example.com\u002Fadmin\u002Flogin\u002F\n",[115,22725,22726,22728,22730],{"class":117,"line":162},[115,22727,14184],{"class":262},[115,22729,21171],{"class":132},[115,22731,22732],{"class":132}," 'systemctl is-active myapp-gunicorn nginx'\n",[115,22734,22735,22737,22739],{"class":117,"line":175},[115,22736,14184],{"class":262},[115,22738,21171],{"class":132},[115,22740,22162],{"class":132},[115,22742,22743,22745,22747],{"class":117,"line":350},[115,22744,14184],{"class":262},[115,22746,21171],{"class":132},[115,22748,22749],{"class":132}," 'readlink -f \u002Fsrv\u002Fmyapp\u002Fcurrent'\n",[16,22751,22752],{},"Confirm:",[63,22754,22755,22759,22764,22769,22772,22775,22778,22781],{},[66,22756,22757],{},[20,22758,2707],{},[66,22760,22761,22763],{},[20,22762,2719],{}," matches your domain",[66,22765,22766,22768],{},[20,22767,2725],{}," includes your HTTPS origin",[66,22770,22771],{},"static files load over HTTPS",[66,22773,22774],{},"Gunicorn stays active after restart",[66,22776,22777],{},"Nginx proxies to Gunicorn successfully",[66,22779,22780],{},"no import or settings errors appear in logs",[66,22782,22783],{},"the active release matches the version you intended to deploy",[11,22785,22787],{"id":22786},"rollback-and-recovery-notes","Rollback and recovery notes",[16,22789,22790,22791,22793],{},"If a release fails, point ",[20,22792,13654],{}," back to a known-good release and restart Gunicorn:",[106,22795,22797],{"className":108,"code":22796,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F\u003Cprevious-release-name> \u002Fsrv\u002Fmyapp\u002Fcurrent\nsystemctl restart myapp-gunicorn\n",[20,22798,22799,22821],{"__ignoreMap":111},[115,22800,22801,22803,22805,22808,22811,22814,22816,22819],{"class":117,"line":118},[115,22802,14854],{"class":262},[115,22804,14857],{"class":202},[115,22806,22807],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F",[115,22809,22810],{"class":121},"\u003C",[115,22812,22813],{"class":132},"previous-release-nam",[115,22815,17377],{"class":125},[115,22817,22818],{"class":121},">",[115,22820,5306],{"class":132},[115,22822,22823,22825,22827],{"class":117,"line":136},[115,22824,1981],{"class":262},[115,22826,3483],{"class":132},[115,22828,22829],{"class":132}," myapp-gunicorn\n",[16,22831,22832],{},"Keep at least one known-good release directory so rollback is actually possible.",[16,22834,22835],{},"If the problem is code-related, redeploy a previous tag:",[106,22837,22839],{"className":2485,"code":22838,"language":2487,"meta":111,"style":111},"deploy_version: \"v1.2.3\"\n",[20,22840,22841],{"__ignoreMap":111},[115,22842,22843,22845,22847],{"class":117,"line":118},[115,22844,20592],{"class":2494},[115,22846,2513],{"class":125},[115,22848,22849],{"class":132},"\"v1.2.3\"\n",[16,22851,22852],{},"Failed migrations are different. If a schema change is irreversible, rolling back code alone may not restore the app. For risky migrations, take a backup first and define whether rollback means restoring the database or shipping a forward fix.",[16,22854,22855,22856,22858],{},"For bad Nginx changes, always keep ",[20,22857,7611],{}," before reload. The same principle applies to systemd units: write the file, reload the daemon, and inspect service status immediately.",[11,22860,1321],{"id":1320},[16,22862,22863],{},"This setup works because Ansible handles both server state and release steps in one repeatable workflow. You can use Ansible to deploy a Django app without turning deployment into a collection of shell notes.",[16,22865,22866,22867,22870],{},"The release-directory pattern gives you a clean rollback path if you retain previous releases. systemd keeps Gunicorn managed correctly. Nginx handles client traffic and static files. Django management commands run from the deployed code with the production environment loaded. That is a good default for ",[1226,22868,22869],{},"production Django deployment with Ansible"," on a single Ubuntu host.",[16,22872,22873],{},"Alternatives exist:",[63,22875,22876,22879,22882,22885],{},[66,22877,22878],{},"deploy from a built artifact instead of Git if you want stricter reproducibility",[66,22880,22881],{},"use a Unix socket instead of TCP for Gunicorn-to-Nginx communication",[66,22883,22884],{},"split static and media to object storage or CDN for larger deployments",[66,22886,22887],{},"move database and Redis off-host as the app grows",[11,22889,1337],{"id":1336},[63,22891,22892,22895,22900,22905,22908,22911,22921],{},[66,22893,22894],{},"If user uploads are stored locally, do not keep them inside release directories. Use a shared media path.",[66,22896,6168,22897,22899],{},[20,22898,13689],{}," output is inside the release, old releases may contain stale assets. A shared static path or CDN-backed strategy may be better later.",[66,22901,22902,22903,211],{},"Large uploads may require Nginx ",[20,22904,13316],{},[66,22906,22907],{},"Long-running requests may require Gunicorn and Nginx timeout tuning.",[66,22909,22910],{},"If private Git access is required, make sure the deploy user has the right SSH key or use an artifact upload workflow instead.",[66,22912,22913,22914,22917,22918,22920],{},"If your Django settings read environment variables with a library like ",[20,22915,22916],{},"django-environ",", make sure the variable names in ",[20,22919,191],{}," match your settings module exactly.",[66,22922,22923],{},"Before enabling the HTTPS Nginx server block, make sure certificates exist at the configured paths or adjust the template for your TLS method.",[11,22925,1386],{"id":1385},[16,22927,22928,22929,211],{},"For the broader production model, see ",[1395,22930,22932],{"href":22931},"\u002Fdeploy","How Django Deployment Works in Production",[16,22934,22935,22936,211],{},"If you want the app-server and reverse-proxy details in isolation, read ",[1395,22937,2986],{"href":2985},[16,22939,22940,22941,211],{},"If you are deploying an ASGI stack instead, read ",[1395,22942,8039],{"href":8038},[16,22944,22945,22946,211],{},"If you want a simpler HTTPS edge setup, read ",[1395,22947,8046],{"href":8045},[16,22949,22950,22951,211],{},"For a final production review, use ",[1395,22952,3000],{"href":2999},[11,22954,1420],{"id":1419},[52,22956,22958],{"id":22957},"how-do-i-store-django-secrets-securely-in-an-ansible-deployment","How do I store Django secrets securely in an Ansible deployment?",[16,22960,22961,22962,22965],{},"Use Ansible Vault or another external secret backend. Render secrets into an environment file on the server with ",[20,22963,22964],{},"0600"," permissions and ownership set to the app user. Do not commit plaintext secrets to Git.",[52,22967,22969],{"id":22968},"should-ansible-run-django-migrations-automatically-during-deploy","Should Ansible run Django migrations automatically during deploy?",[16,22971,22972],{},"Usually yes, but only if you understand the migration risk. For simple additive schema changes, automated migrations are common. For destructive or high-impact changes, add a backup step and a deliberate release procedure.",[52,22974,22976],{"id":22975},"is-it-better-to-deploy-django-from-git-or-from-a-built-artifact","Is it better to deploy Django from Git or from a built artifact?",[16,22978,22979],{},"Git is simpler for small teams and single-server deployments. Built artifacts are often better when you need stricter reproducibility, supply-chain controls, or identical releases across many servers.",[52,22981,22983],{"id":22982},"how-do-i-roll-back-if-the-new-django-release-fails-after-restart","How do I roll back if the new Django release fails after restart?",[16,22985,22986,22987,22989],{},"Switch the ",[20,22988,13654],{}," symlink back to the previous known-good release and restart Gunicorn. If the failure involved a database migration, code rollback alone may not be enough, so your release process should define backup and restore expectations.",[52,22991,22993],{"id":22992},"can-i-use-the-same-ansible-structure-for-staging-and-production","Can I use the same Ansible structure for staging and production?",[16,22995,22996,22997,23000],{},"Yes. Keep separate inventory and variable files for each environment. That is usually the first step toward a reusable ",[1226,22998,22999],{},"Ansible playbook for Django deployment"," across multiple servers.",[1485,23002,23003],{},"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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}",{"title":111,"searchDepth":149,"depth":149,"links":23005},[23006,23007,23008,23009,23010,23014,23015,23016,23017,23018,23019,23022,23023,23024,23025,23026,23027],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":20378,"depth":136,"text":20379},{"id":20426,"depth":136,"text":20427,"children":23011},[23012,23013],{"id":20439,"depth":149,"text":20440},{"id":12381,"depth":149,"text":12382},{"id":20771,"depth":136,"text":20772},{"id":21180,"depth":136,"text":21181},{"id":21587,"depth":136,"text":21588},{"id":21876,"depth":136,"text":14116},{"id":22171,"depth":136,"text":22172},{"id":22636,"depth":136,"text":22637,"children":23020},[23021],{"id":22682,"depth":149,"text":22683},{"id":22689,"depth":136,"text":22690},{"id":22786,"depth":136,"text":22787},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":23028},[23029,23030,23031,23032,23033],{"id":22957,"depth":149,"text":22958},{"id":22968,"depth":149,"text":22969},{"id":22975,"depth":149,"text":22976},{"id":22982,"depth":149,"text":22983},{"id":22992,"depth":149,"text":22993},"Manual Django deployment usually starts simple and becomes unreliable fast. You SSH into a server, pull code, install packages, update environment variables, run migrations, res...","advanced","BOFU",{},"\u002Fdjango-deploy-with-ansible","25",[2985,14027,23041],"\u002Fdeploy\u002Fdeploy-django-on-aws-ec2",{"title":20267,"description":23034},[1557,21854,2156,14954],"django-deploy-with-ansible",[1557,21854,2156,14954],"XJQQHzmm14V1wcUQ59M9eDu_No7jJVbsfWT4_h7geXA",{"id":23048,"title":8046,"body":23049,"category":3088,"description":24936,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":24937,"navigation":309,"path":24938,"priority":24939,"related":24940,"role":1553,"section":3098,"seo":24941,"stack":24942,"stem":24943,"tags":24944,"type":1561,"__hash__":24945},"articles\u002Fdeploy-django-with-caddy-https.md",{"type":8,"value":23050,"toc":24911},[23051,23053,23056,23059,23061,23064,23098,23101,23103,23107,23110,23155,23158,23172,23175,23179,23182,23307,23310,23322,23325,23328,23353,23355,23378,23381,23407,23410,23413,23448,23452,23481,23485,23488,23492,23495,23579,23582,23612,23615,23628,23637,23640,23654,23763,23766,23801,23804,23823,23827,23844,23847,23865,23874,23878,23881,23929,23932,23946,23949,24007,24010,24082,24092,24095,24115,24118,24133,24136,24155,24159,24178,24191,24196,24208,24216,24220,24227,24231,24234,24248,24251,24270,24273,24277,24280,24304,24307,24331,24334,24380,24384,24387,24442,24445,24472,24475,24496,24498,24507,24510,24513,24530,24532,24591,24594,24614,24617,24637,24648,24650,24652,24654,24672,24675,24681,24687,24694,24696,24698,24748,24750,24752,24757,24762,24767,24777,24779,24781,24785,24788,24792,24794,24843,24849,24853,24856,24860,24863,24881,24883,24901,24905,24908],[11,23052,14],{"id":13},[16,23054,23055],{},"If you want to deploy Django with Caddy in production, the main challenge is not just putting a reverse proxy in front of the app. You need a complete path that covers app process management, automatic HTTPS, Django proxy settings, static files, migrations, service supervision, and a safe rollback path.",[16,23057,23058],{},"A partial setup often fails in predictable ways: Gunicorn is exposed publicly, HTTPS works but Django still thinks requests are insecure, CSRF breaks on form POSTs, static files return 404, or a bad config reload takes the site down. The goal is to run Django behind Caddy safely so TLS is automatic, the app only listens locally, and updates are repeatable.",[11,23060,30],{"id":29},[16,23062,23063],{},"A reliable Django Caddy deployment looks like this:",[63,23065,23066,23071,23078,23081,23087,23090,23095],{},[66,23067,23068,23069,6411],{},"run Django with Gunicorn on ",[20,23070,20396],{},[66,23072,23073,23074,3146,23076],{},"put Caddy in front as the only public entry point on ports ",[20,23075,3808],{},[20,23077,2174],{},[66,23079,23080],{},"let Caddy issue and renew TLS certificates automatically",[66,23082,23083,23084,23086],{},"configure Django for reverse proxy HTTPS with ",[20,23085,2377],{}," and correct hosts",[66,23088,23089],{},"collect static files to a stable path and either let Caddy serve them from disk or move them to object storage",[66,23091,23092,23093],{},"manage Gunicorn with ",[20,23094,1277],{},[66,23096,23097],{},"validate Caddy config before reload and verify HTTPS, redirects, CSRF, and static assets after deploy",[23099,23100],"hr",{},[11,23102,43],{"id":42},[11,23104,23106],{"id":23105},"_1-architecture-for-deploying-django-with-caddy","1. Architecture for deploying Django with Caddy",[16,23108,23109],{},"A practical production layout looks like this:",[63,23111,23112,23117,23123,23129,23136,23140,23147,23150],{},[66,23113,23114,23115],{},"app code release at ",[20,23116,14814],{},[66,23118,23119,23120],{},"Python virtualenv at ",[20,23121,23122],{},"\u002Fsrv\u002Fmyapp\u002Fvenv",[66,23124,23125,23126],{},"shared static files at ",[20,23127,23128],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic",[66,23130,23131,23132,23135],{},"shared media files at ",[20,23133,23134],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia"," if you keep uploads on local disk",[66,23137,20393,23138],{},[20,23139,20396],{},[66,23141,23142,23143,3146,23145],{},"Caddy listening publicly on ",[20,23144,3808],{},[20,23146,2174],{},[66,23148,23149],{},"PostgreSQL and Redis running separately if used",[66,23151,23152,23154],{},[20,23153,1277],{}," managing Gunicorn and Caddy",[16,23156,23157],{},"Why Caddy fits this setup:",[63,23159,23160,23163,23166,23169],{},[66,23161,23162],{},"automatic HTTPS by default",[66,23164,23165],{},"simple reverse proxy configuration",[66,23167,23168],{},"built-in HTTP to HTTPS redirects",[66,23170,23171],{},"less TLS maintenance than manual certificate management",[16,23173,23174],{},"Deployment invariant: Caddy should be the only public entry point. Gunicorn should never listen on a public interface.",[11,23176,23178],{"id":23177},"_2-prepare-the-django-app-for-production","2. Prepare the Django app for production",[16,23180,23181],{},"Update Django settings before exposing the app.",[106,23183,23185],{"className":2369,"code":23184,"language":1114,"meta":111,"style":111},"DEBUG = False\n\nALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\", \"https:\u002F\u002Fwww.example.com\"]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\"\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = \"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\"\n",[20,23186,23187,23195,23199,23215,23231,23235,23251,23259,23267,23271,23279,23287,23291,23299],{"__ignoreMap":111},[115,23188,23189,23191,23193],{"class":117,"line":118},[115,23190,7350],{"class":202},[115,23192,2380],{"class":121},[115,23194,7355],{"class":202},[115,23196,23197],{"class":117,"line":136},[115,23198,310],{"emptyLinePlaceholder":309},[115,23200,23201,23203,23205,23207,23209,23211,23213],{"class":117,"line":149},[115,23202,2719],{"class":202},[115,23204,2380],{"class":121},[115,23206,7493],{"class":125},[115,23208,7496],{"class":132},[115,23210,1153],{"class":125},[115,23212,7501],{"class":132},[115,23214,2552],{"class":125},[115,23216,23217,23219,23221,23223,23225,23227,23229],{"class":117,"line":162},[115,23218,2725],{"class":202},[115,23220,2380],{"class":121},[115,23222,7493],{"class":125},[115,23224,15110],{"class":132},[115,23226,1153],{"class":125},[115,23228,15115],{"class":132},[115,23230,2552],{"class":125},[115,23232,23233],{"class":117,"line":175},[115,23234,310],{"emptyLinePlaceholder":309},[115,23236,23237,23239,23241,23243,23245,23247,23249],{"class":117,"line":350},[115,23238,2377],{"class":202},[115,23240,2380],{"class":121},[115,23242,2383],{"class":125},[115,23244,2386],{"class":132},[115,23246,1153],{"class":125},[115,23248,2391],{"class":132},[115,23250,2394],{"class":125},[115,23252,23253,23255,23257],{"class":117,"line":365},[115,23254,2417],{"class":202},[115,23256,2380],{"class":121},[115,23258,2412],{"class":202},[115,23260,23261,23263,23265],{"class":117,"line":380},[115,23262,2426],{"class":202},[115,23264,2380],{"class":121},[115,23266,2412],{"class":202},[115,23268,23269],{"class":117,"line":487},[115,23270,310],{"emptyLinePlaceholder":309},[115,23272,23273,23275,23277],{"class":117,"line":2095},[115,23274,11908],{"class":202},[115,23276,2380],{"class":121},[115,23278,11913],{"class":132},[115,23280,23281,23283,23285],{"class":117,"line":2104},[115,23282,11918],{"class":202},[115,23284,2380],{"class":121},[115,23286,15195],{"class":132},[115,23288,23289],{"class":117,"line":2113},[115,23290,310],{"emptyLinePlaceholder":309},[115,23292,23293,23295,23297],{"class":117,"line":2122},[115,23294,15204],{"class":202},[115,23296,2380],{"class":121},[115,23298,15209],{"class":132},[115,23300,23301,23303,23305],{"class":117,"line":2131},[115,23302,15214],{"class":202},[115,23304,2380],{"class":121},[115,23306,15219],{"class":132},[16,23308,23309],{},"If you want Django itself to enforce HTTPS redirects, you can add:",[106,23311,23312],{"className":2369,"code":7414,"language":1114,"meta":111,"style":111},[20,23313,23314],{"__ignoreMap":111},[115,23315,23316,23318,23320],{"class":117,"line":118},[115,23317,2407],{"class":202},[115,23319,2380],{"class":121},[115,23321,2412],{"class":202},[16,23323,23324],{},"That is optional when Caddy already redirects HTTP to HTTPS, but it adds protection if traffic reaches Django through an unexpected internal path.",[16,23326,23327],{},"Store secrets outside the repository. A simple systemd environment file works well:",[106,23329,23331],{"className":108,"code":23330,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fetc\u002Fmyapp\nsudo nano \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[20,23332,23333,23344],{"__ignoreMap":111},[115,23334,23335,23337,23339,23341],{"class":117,"line":118},[115,23336,2001],{"class":262},[115,23338,6721],{"class":132},[115,23340,1001],{"class":202},[115,23342,23343],{"class":132}," \u002Fetc\u002Fmyapp\n",[115,23345,23346,23348,23350],{"class":117,"line":136},[115,23347,2001],{"class":262},[115,23349,12408],{"class":132},[115,23351,23352],{"class":132}," \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[16,23354,12414],{},[106,23356,23358],{"className":2329,"code":23357,"language":2331,"meta":111,"style":111},"DJANGO_SECRET_KEY=replace-this\nDJANGO_SETTINGS_MODULE=config.settings.production\nDATABASE_URL=postgres:\u002F\u002Fmyuser:mypassword@127.0.0.1:5432\u002Fmyapp\nALLOWED_HOSTS=example.com,www.example.com\n",[20,23359,23360,23365,23369,23374],{"__ignoreMap":111},[115,23361,23362],{"class":117,"line":118},[115,23363,23364],{},"DJANGO_SECRET_KEY=replace-this\n",[115,23366,23367],{"class":117,"line":136},[115,23368,2338],{},[115,23370,23371],{"class":117,"line":149},[115,23372,23373],{},"DATABASE_URL=postgres:\u002F\u002Fmyuser:mypassword@127.0.0.1:5432\u002Fmyapp\n",[115,23375,23376],{"class":117,"line":162},[115,23377,2358],{},[16,23379,23380],{},"Protect it:",[106,23382,23384],{"className":108,"code":23383,"language":110,"meta":111,"style":111},"sudo chown root:root \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chmod 600 \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[20,23385,23386,23397],{"__ignoreMap":111},[115,23387,23388,23390,23392,23395],{"class":117,"line":118},[115,23389,2001],{"class":262},[115,23391,6733],{"class":132},[115,23393,23394],{"class":132}," root:root",[115,23396,23352],{"class":132},[115,23398,23399,23401,23403,23405],{"class":117,"line":136},[115,23400,2001],{"class":262},[115,23402,12480],{"class":132},[115,23404,266],{"class":202},[115,23406,23352],{"class":132},[16,23408,23409],{},"Make sure your Django settings actually read environment variables if you use them in the env file.",[16,23411,23412],{},"Run migrations and collect static during deployment, not manually in the middle of troubleshooting:",[106,23414,23416],{"className":108,"code":23415,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npython manage.py migrate\npython manage.py collectstatic --noinput\n",[20,23417,23418,23424,23430,23438],{"__ignoreMap":111},[115,23419,23420,23422],{"class":117,"line":118},[115,23421,5303],{"class":202},[115,23423,5306],{"class":132},[115,23425,23426,23428],{"class":117,"line":136},[115,23427,5311],{"class":202},[115,23429,5314],{"class":132},[115,23431,23432,23434,23436],{"class":117,"line":149},[115,23433,1114],{"class":262},[115,23435,1117],{"class":132},[115,23437,11324],{"class":132},[115,23439,23440,23442,23444,23446],{"class":117,"line":162},[115,23441,1114],{"class":262},[115,23443,1117],{"class":132},[115,23445,1838],{"class":132},[115,23447,1841],{"class":202},[16,23449,23450],{},[1226,23451,4678],{},[63,23453,23454,23459,23464,23471,23476],{},[66,23455,23456,23458],{},[20,23457,7350],{}," is off",[66,23460,23461,23463],{},[20,23462,2719],{}," includes the real domain",[66,23465,23466,23468,23469],{},[20,23467,2725],{}," uses ",[20,23470,16092],{},[66,23472,23473,23475],{},[20,23474,11918],{}," points to the same path your proxy setup expects",[66,23477,23478,23480],{},[20,23479,13689],{}," completes without storage errors",[16,23482,23483],{},[1226,23484,4956],{},[16,23486,23487],{},"If migrations are risky, take a database backup first. Code rollback is usually easier than schema rollback.",[11,23489,23491],{"id":23490},"_3-install-and-configure-gunicorn","3. Install and configure Gunicorn",[16,23493,23494],{},"Create the virtualenv and install dependencies:",[106,23496,23498],{"className":108,"code":23497,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fsrv\u002Fmyapp\nsudo chown -R deploy:deploy \u002Fsrv\u002Fmyapp\n\ncd \u002Fsrv\u002Fmyapp\npython3 -m venv venv\nsource venv\u002Fbin\u002Factivate\npip install --upgrade pip\npip install -r \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Frequirements.txt\npip install gunicorn\n",[20,23499,23500,23510,23522,23526,23532,23543,23550,23560,23571],{"__ignoreMap":111},[115,23501,23502,23504,23506,23508],{"class":117,"line":118},[115,23503,2001],{"class":262},[115,23505,6721],{"class":132},[115,23507,1001],{"class":202},[115,23509,14799],{"class":132},[115,23511,23512,23514,23516,23518,23520],{"class":117,"line":136},[115,23513,2001],{"class":262},[115,23515,6733],{"class":132},[115,23517,6736],{"class":202},[115,23519,14264],{"class":132},[115,23521,14799],{"class":132},[115,23523,23524],{"class":117,"line":149},[115,23525,310],{"emptyLinePlaceholder":309},[115,23527,23528,23530],{"class":117,"line":162},[115,23529,5303],{"class":202},[115,23531,14799],{"class":132},[115,23533,23534,23536,23538,23540],{"class":117,"line":175},[115,23535,12281],{"class":262},[115,23537,12284],{"class":202},[115,23539,12287],{"class":132},[115,23541,23542],{"class":132}," venv\n",[115,23544,23545,23547],{"class":117,"line":350},[115,23546,5311],{"class":202},[115,23548,23549],{"class":132}," venv\u002Fbin\u002Factivate\n",[115,23551,23552,23554,23556,23558],{"class":117,"line":365},[115,23553,8618],{"class":262},[115,23555,6600],{"class":132},[115,23557,12338],{"class":202},[115,23559,12341],{"class":132},[115,23561,23562,23564,23566,23568],{"class":117,"line":380},[115,23563,8618],{"class":262},[115,23565,6600],{"class":132},[115,23567,12350],{"class":202},[115,23569,23570],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Frequirements.txt\n",[115,23572,23573,23575,23577],{"class":117,"line":487},[115,23574,8618],{"class":262},[115,23576,6600],{"class":132},[115,23578,1987],{"class":132},[16,23580,23581],{},"Test Gunicorn manually before adding Caddy:",[106,23583,23585],{"className":108,"code":23584,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\ngunicorn --bind 127.0.0.1:8000 config.wsgi:application\n",[20,23586,23587,23593,23599],{"__ignoreMap":111},[115,23588,23589,23591],{"class":117,"line":118},[115,23590,5303],{"class":202},[115,23592,5306],{"class":132},[115,23594,23595,23597],{"class":117,"line":136},[115,23596,5311],{"class":202},[115,23598,5314],{"class":132},[115,23600,23601,23603,23606,23609],{"class":117,"line":149},[115,23602,14954],{"class":262},[115,23604,23605],{"class":202}," --bind",[115,23607,23608],{"class":132}," 127.0.0.1:8000",[115,23610,23611],{"class":132}," config.wsgi:application\n",[16,23613,23614],{},"In another shell on the server:",[106,23616,23618],{"className":108,"code":23617,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002F127.0.0.1:8000\u002F\n",[20,23619,23620],{"__ignoreMap":111},[115,23621,23622,23624,23626],{"class":117,"line":118},[115,23623,2764],{"class":262},[115,23625,2767],{"class":202},[115,23627,3950],{"class":132},[16,23629,23630,23631,4493,23633,23636],{},"You should get ",[20,23632,17741],{},[20,23634,23635],{},"302",", depending on your app.",[16,23638,23639],{},"Create a systemd service:",[106,23641,23643],{"className":108,"code":23642,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\n",[20,23644,23645],{"__ignoreMap":111},[115,23646,23647,23649,23651],{"class":117,"line":118},[115,23648,2001],{"class":262},[115,23650,12408],{"class":132},[115,23652,23653],{"class":132}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\n",[106,23655,23657],{"className":2026,"code":23656,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for myapp\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn \\\n    --workers 3 \\\n    --bind 127.0.0.1:8000 \\\n    --access-logfile - \\\n    --error-logfile - \\\n    config.wsgi:application\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n",[20,23658,23659,23663,23669,23675,23679,23683,23689,23695,23701,23707,23714,23718,23723,23728,23733,23737,23743,23749,23753,23757],{"__ignoreMap":111},[115,23660,23661],{"class":117,"line":118},[115,23662,2035],{"class":262},[115,23664,23665,23667],{"class":117,"line":136},[115,23666,2040],{"class":121},[115,23668,15353],{"class":125},[115,23670,23671,23673],{"class":117,"line":149},[115,23672,2048],{"class":121},[115,23674,2051],{"class":125},[115,23676,23677],{"class":117,"line":162},[115,23678,310],{"emptyLinePlaceholder":309},[115,23680,23681],{"class":117,"line":175},[115,23682,2060],{"class":262},[115,23684,23685,23687],{"class":117,"line":350},[115,23686,2065],{"class":121},[115,23688,12548],{"class":125},[115,23690,23691,23693],{"class":117,"line":365},[115,23692,2073],{"class":121},[115,23694,2076],{"class":125},[115,23696,23697,23699],{"class":117,"line":380},[115,23698,2081],{"class":121},[115,23700,4905],{"class":125},[115,23702,23703,23705],{"class":117,"line":487},[115,23704,2089],{"class":121},[115,23706,4912],{"class":125},[115,23708,23709,23711],{"class":117,"line":2095},[115,23710,2107],{"class":121},[115,23712,23713],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn \\\n",[115,23715,23716],{"class":117,"line":2104},[115,23717,15417],{"class":125},[115,23719,23720],{"class":117,"line":2113},[115,23721,23722],{"class":125},"    --bind 127.0.0.1:8000 \\\n",[115,23724,23725],{"class":117,"line":2122},[115,23726,23727],{"class":125},"    --access-logfile - \\\n",[115,23729,23730],{"class":117,"line":2131},[115,23731,23732],{"class":125},"    --error-logfile - \\\n",[115,23734,23735],{"class":117,"line":2136},[115,23736,15427],{"class":125},[115,23738,23739,23741],{"class":117,"line":2142},[115,23740,2116],{"class":121},[115,23742,4932],{"class":125},[115,23744,23745,23747],{"class":117,"line":2273},[115,23746,2125],{"class":121},[115,23748,2128],{"class":125},[115,23750,23751],{"class":117,"line":2282},[115,23752,310],{"emptyLinePlaceholder":309},[115,23754,23755],{"class":117,"line":2291},[115,23756,2139],{"class":262},[115,23758,23759,23761],{"class":117,"line":2299},[115,23760,2145],{"class":121},[115,23762,2148],{"class":125},[16,23764,23765],{},"Load and start it:",[106,23767,23769],{"className":108,"code":23768,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable --now gunicorn\nsudo systemctl status gunicorn\n",[20,23770,23771,23779,23791],{"__ignoreMap":111},[115,23772,23773,23775,23777],{"class":117,"line":118},[115,23774,2001],{"class":262},[115,23776,3480],{"class":132},[115,23778,4984],{"class":132},[115,23780,23781,23783,23785,23787,23789],{"class":117,"line":136},[115,23782,2001],{"class":262},[115,23784,3480],{"class":132},[115,23786,8567],{"class":132},[115,23788,9433],{"class":202},[115,23790,1987],{"class":132},[115,23792,23793,23795,23797,23799],{"class":117,"line":149},[115,23794,2001],{"class":262},[115,23796,3480],{"class":132},[115,23798,1984],{"class":132},[115,23800,1987],{"class":132},[16,23802,23803],{},"Check logs if needed:",[106,23805,23807],{"className":108,"code":23806,"language":110,"meta":111,"style":111},"journalctl -u gunicorn -n 50 --no-pager\n",[20,23808,23809],{"__ignoreMap":111},[115,23810,23811,23813,23815,23817,23819,23821],{"class":117,"line":118},[115,23812,2785],{"class":262},[115,23814,2788],{"class":202},[115,23816,2791],{"class":132},[115,23818,2794],{"class":202},[115,23820,15523],{"class":202},[115,23822,2800],{"class":202},[16,23824,23825],{},[1226,23826,4678],{},[63,23828,23829,23835,23841],{},[66,23830,23831,23832],{},"Gunicorn is active in ",[20,23833,23834],{},"systemctl status",[66,23836,23837,23840],{},[20,23838,23839],{},"curl http:\u002F\u002F127.0.0.1:8000\u002F"," works locally",[66,23842,23843],{},"Gunicorn is not listening on a public interface",[16,23845,23846],{},"Check listening ports:",[106,23848,23850],{"className":108,"code":23849,"language":110,"meta":111,"style":111},"ss -tulpn | grep 8000\n",[20,23851,23852],{"__ignoreMap":111},[115,23853,23854,23856,23858,23860,23862],{"class":117,"line":118},[115,23855,6472],{"class":262},[115,23857,6475],{"class":202},[115,23859,579],{"class":121},[115,23861,4838],{"class":262},[115,23863,23864],{"class":202}," 8000\n",[16,23866,23867,23868,23870,23871,211],{},"You want ",[20,23869,20396],{},", not ",[20,23872,23873],{},"0.0.0.0:8000",[11,23875,23877],{"id":23876},"_4-install-caddy-and-configure-automatic-https","4. Install Caddy and configure automatic HTTPS",[16,23879,23880],{},"Install Caddy using your distro package source. On Debian or Ubuntu, if Caddy is already available from your configured repositories:",[106,23882,23884],{"className":108,"code":23883,"language":110,"meta":111,"style":111},"sudo apt update\nsudo apt install -y caddy\nsudo systemctl enable --now caddy\nsudo systemctl status caddy\n",[20,23885,23886,23894,23907,23919],{"__ignoreMap":111},[115,23887,23888,23890,23892],{"class":117,"line":118},[115,23889,2001],{"class":262},[115,23891,6588],{"class":132},[115,23893,6591],{"class":132},[115,23895,23896,23898,23900,23902,23904],{"class":117,"line":136},[115,23897,2001],{"class":262},[115,23899,6588],{"class":132},[115,23901,6600],{"class":132},[115,23903,8432],{"class":202},[115,23905,23906],{"class":132}," caddy\n",[115,23908,23909,23911,23913,23915,23917],{"class":117,"line":149},[115,23910,2001],{"class":262},[115,23912,3480],{"class":132},[115,23914,8567],{"class":132},[115,23916,9433],{"class":202},[115,23918,23906],{"class":132},[115,23920,23921,23923,23925,23927],{"class":117,"line":162},[115,23922,2001],{"class":262},[115,23924,3480],{"class":132},[115,23926,1984],{"class":132},[115,23928,23906],{"class":132},[16,23930,23931],{},"Create the Caddyfile:",[106,23933,23935],{"className":108,"code":23934,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fcaddy\u002FCaddyfile\n",[20,23936,23937],{"__ignoreMap":111},[115,23938,23939,23941,23943],{"class":117,"line":118},[115,23940,2001],{"class":262},[115,23942,12408],{"class":132},[115,23944,23945],{"class":132}," \u002Fetc\u002Fcaddy\u002FCaddyfile\n",[16,23947,23948],{},"Minimal reverse proxy setup:",[106,23950,23954],{"className":23951,"code":23952,"language":23953,"meta":111,"style":111},"language-caddy shiki shiki-themes github-light github-dark","example.com, www.example.com {\n    encode gzip zstd\n\n    header {\n        X-Content-Type-Options nosniff\n        X-Frame-Options SAMEORIGIN\n        Referrer-Policy strict-origin-when-cross-origin\n    }\n\n    reverse_proxy 127.0.0.1:8000\n}\n","caddy",[20,23955,23956,23961,23966,23970,23975,23980,23985,23990,23994,23998,24003],{"__ignoreMap":111},[115,23957,23958],{"class":117,"line":118},[115,23959,23960],{},"example.com, www.example.com {\n",[115,23962,23963],{"class":117,"line":136},[115,23964,23965],{},"    encode gzip zstd\n",[115,23967,23968],{"class":117,"line":149},[115,23969,310],{"emptyLinePlaceholder":309},[115,23971,23972],{"class":117,"line":162},[115,23973,23974],{},"    header {\n",[115,23976,23977],{"class":117,"line":175},[115,23978,23979],{},"        X-Content-Type-Options nosniff\n",[115,23981,23982],{"class":117,"line":350},[115,23983,23984],{},"        X-Frame-Options SAMEORIGIN\n",[115,23986,23987],{"class":117,"line":365},[115,23988,23989],{},"        Referrer-Policy strict-origin-when-cross-origin\n",[115,23991,23992],{"class":117,"line":380},[115,23993,2233],{},[115,23995,23996],{"class":117,"line":487},[115,23997,310],{"emptyLinePlaceholder":309},[115,23999,24000],{"class":117,"line":2095},[115,24001,24002],{},"    reverse_proxy 127.0.0.1:8000\n",[115,24004,24005],{"class":117,"line":2104},[115,24006,2323],{},[16,24008,24009],{},"If you want Caddy to serve collected static files directly from disk:",[106,24011,24013],{"className":23951,"code":24012,"language":23953,"meta":111,"style":111},"example.com, www.example.com {\n    encode gzip zstd\n\n    header {\n        X-Content-Type-Options nosniff\n        X-Frame-Options SAMEORIGIN\n        Referrer-Policy strict-origin-when-cross-origin\n    }\n\n    handle \u002Fstatic\u002F* {\n        root * \u002Fsrv\u002Fmyapp\u002Fshared\n        file_server\n    }\n\n    reverse_proxy 127.0.0.1:8000\n}\n",[20,24014,24015,24019,24023,24027,24031,24035,24039,24043,24047,24051,24056,24061,24066,24070,24074,24078],{"__ignoreMap":111},[115,24016,24017],{"class":117,"line":118},[115,24018,23960],{},[115,24020,24021],{"class":117,"line":136},[115,24022,23965],{},[115,24024,24025],{"class":117,"line":149},[115,24026,310],{"emptyLinePlaceholder":309},[115,24028,24029],{"class":117,"line":162},[115,24030,23974],{},[115,24032,24033],{"class":117,"line":175},[115,24034,23979],{},[115,24036,24037],{"class":117,"line":350},[115,24038,23984],{},[115,24040,24041],{"class":117,"line":365},[115,24042,23989],{},[115,24044,24045],{"class":117,"line":380},[115,24046,2233],{},[115,24048,24049],{"class":117,"line":487},[115,24050,310],{"emptyLinePlaceholder":309},[115,24052,24053],{"class":117,"line":2095},[115,24054,24055],{},"    handle \u002Fstatic\u002F* {\n",[115,24057,24058],{"class":117,"line":2104},[115,24059,24060],{},"        root * \u002Fsrv\u002Fmyapp\u002Fshared\n",[115,24062,24063],{"class":117,"line":2113},[115,24064,24065],{},"        file_server\n",[115,24067,24068],{"class":117,"line":2122},[115,24069,2233],{},[115,24071,24072],{"class":117,"line":2131},[115,24073,310],{"emptyLinePlaceholder":309},[115,24075,24076],{"class":117,"line":2136},[115,24077,24002],{},[115,24079,24080],{"class":117,"line":2142},[115,24081,2323],{},[16,24083,24084,24085,24088,24089,211],{},"With this layout, requests for ",[20,24086,24087],{},"\u002Fstatic\u002F..."," map correctly to files under ",[20,24090,24091],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\u002F...",[16,24093,24094],{},"Validate before reload:",[106,24096,24098],{"className":108,"code":24097,"language":110,"meta":111,"style":111},"sudo caddy validate --config \u002Fetc\u002Fcaddy\u002FCaddyfile\n",[20,24099,24100],{"__ignoreMap":111},[115,24101,24102,24104,24107,24110,24113],{"class":117,"line":118},[115,24103,2001],{"class":262},[115,24105,24106],{"class":132}," caddy",[115,24108,24109],{"class":132}," validate",[115,24111,24112],{"class":202}," --config",[115,24114,23945],{"class":132},[16,24116,24117],{},"Reload safely:",[106,24119,24121],{"className":108,"code":24120,"language":110,"meta":111,"style":111},"sudo systemctl reload caddy\n",[20,24122,24123],{"__ignoreMap":111},[115,24124,24125,24127,24129,24131],{"class":117,"line":118},[115,24126,2001],{"class":262},[115,24128,3480],{"class":132},[115,24130,3919],{"class":132},[115,24132,23906],{"class":132},[16,24134,24135],{},"Inspect logs if certificate issuance fails:",[106,24137,24139],{"className":108,"code":24138,"language":110,"meta":111,"style":111},"journalctl -u caddy -n 100 --no-pager\n",[20,24140,24141],{"__ignoreMap":111},[115,24142,24143,24145,24147,24149,24151,24153],{"class":117,"line":118},[115,24144,2785],{"class":262},[115,24146,2788],{"class":202},[115,24148,24106],{"class":132},[115,24150,2794],{"class":202},[115,24152,2797],{"class":202},[115,24154,2800],{"class":202},[16,24156,24157],{},[1226,24158,4678],{},[63,24160,24161,24167,24175],{},[66,24162,24163,24164,24166],{},"DNS for ",[20,24165,3145],{}," points to the server",[66,24168,24169,24170,3146,24172,24174],{},"ports ",[20,24171,3808],{},[20,24173,2174],{}," are reachable externally",[66,24176,24177],{},"HTTP redirects to HTTPS:",[106,24179,24181],{"className":108,"code":24180,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\n",[20,24182,24183],{"__ignoreMap":111},[115,24184,24185,24187,24189],{"class":117,"line":118},[115,24186,2764],{"class":262},[115,24188,2767],{"class":202},[115,24190,6494],{"class":132},[63,24192,24193],{},[66,24194,24195],{},"HTTPS responds successfully:",[106,24197,24198],{"className":108,"code":13378,"language":110,"meta":111,"style":111},[20,24199,24200],{"__ignoreMap":111},[115,24201,24202,24204,24206],{"class":117,"line":118},[115,24203,2764],{"class":262},[115,24205,2767],{"class":202},[115,24207,2770],{"class":132},[16,24209,24210,24211,4493,24213,24215],{},"Expected result: an HTTP redirect on port 80, then ",[20,24212,17741],{},[20,24214,23635],{}," on HTTPS.",[16,24217,24218],{},[1226,24219,4956],{},[16,24221,24222,24223,24226],{},"If a Caddy config change breaks the site, restore the previous ",[20,24224,24225],{},"\u002Fetc\u002Fcaddy\u002FCaddyfile",", validate it, then reload Caddy. You do not need to restart Gunicorn for a proxy-only issue.",[11,24228,24230],{"id":24229},"_5-serve-static-and-media-files-correctly","5. Serve static and media files correctly",[16,24232,24233],{},"For static files, common options are:",[63,24235,24236,24242],{},[66,24237,24238,24241],{},[1226,24239,24240],{},"Caddy serves collected static files from disk",": simple for single-server deployments",[66,24243,24244,24247],{},[1226,24245,24246],{},"Object storage\u002FCDN",": better when you want shared or scalable asset delivery",[16,24249,24250],{},"If using local static files:",[63,24252,24253,24261,24264],{},[66,24254,24255,24257,24258,24260],{},[20,24256,13689],{}," must write to the same ",[20,24259,11918],{}," Caddy serves",[66,24262,24263],{},"keep static files outside the individual release directory if you want safer rollbacks",[66,24265,24266,24267,24269],{},"make sure the Caddy user can read the ",[20,24268,11918],{}," directory and files",[16,24271,24272],{},"Media files need more caution. Local disk works for small deployments, but it complicates backups, multi-server scaling, and disaster recovery. If user uploads matter, plan backup and restore explicitly or move media to object storage.",[11,24274,24276],{"id":24275},"_6-security-checks-for-django-behind-caddy","6. Security checks for Django behind Caddy",[16,24278,24279],{},"Important Django settings behind a reverse proxy:",[63,24281,24282,24286,24290,24294,24299],{},[66,24283,24284],{},[20,24285,13869],{},[66,24287,24288],{},[20,24289,22626],{},[66,24291,24292],{},[20,24293,22629],{},[66,24295,24296,24298],{},[20,24297,2725],{}," includes your HTTPS domain",[66,24300,24301,24303],{},[20,24302,2719],{}," includes only your expected hostnames",[16,24305,24306],{},"Important Caddy and network checks:",[63,24308,24309,24312,24315,24318],{},[66,24310,24311],{},"Caddy is the only public entry point",[66,24313,24314],{},"Gunicorn binds only to localhost or a Unix socket",[66,24316,24317],{},"do not expose a direct external path to Gunicorn",[66,24319,24320,24321,1153,24324,24327,24328],{},"firewall allows only ",[20,24322,24323],{},"80\u002Ftcp",[20,24325,24326],{},"443\u002Ftcp",", and optionally ",[20,24329,24330],{},"22\u002Ftcp",[16,24332,24333],{},"For example with UFW:",[106,24335,24337],{"className":108,"code":24336,"language":110,"meta":111,"style":111},"sudo ufw allow 80\u002Ftcp\nsudo ufw allow 443\u002Ftcp\nsudo ufw allow 22\u002Ftcp\nsudo ufw status\n",[20,24338,24339,24350,24361,24372],{"__ignoreMap":111},[115,24340,24341,24343,24345,24347],{"class":117,"line":118},[115,24342,2001],{"class":262},[115,24344,2014],{"class":132},[115,24346,14341],{"class":132},[115,24348,24349],{"class":132}," 80\u002Ftcp\n",[115,24351,24352,24354,24356,24358],{"class":117,"line":136},[115,24353,2001],{"class":262},[115,24355,2014],{"class":132},[115,24357,14341],{"class":132},[115,24359,24360],{"class":132}," 443\u002Ftcp\n",[115,24362,24363,24365,24367,24369],{"class":117,"line":149},[115,24364,2001],{"class":262},[115,24366,2014],{"class":132},[115,24368,14341],{"class":132},[115,24370,24371],{"class":132}," 22\u002Ftcp\n",[115,24373,24374,24376,24378],{"class":117,"line":162},[115,24375,2001],{"class":262},[115,24377,2014],{"class":132},[115,24379,2017],{"class":132},[11,24381,24383],{"id":24382},"_7-release-workflow-for-updates","7. Release workflow for updates",[16,24385,24386],{},"A practical deploy sequence for an in-place setup:",[106,24388,24390],{"className":108,"code":24389,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install -r requirements.txt\npython manage.py migrate\npython manage.py collectstatic --noinput\nsudo systemctl restart gunicorn\n",[20,24391,24392,24398,24404,24414,24422,24432],{"__ignoreMap":111},[115,24393,24394,24396],{"class":117,"line":118},[115,24395,5303],{"class":202},[115,24397,5306],{"class":132},[115,24399,24400,24402],{"class":117,"line":136},[115,24401,5311],{"class":202},[115,24403,5314],{"class":132},[115,24405,24406,24408,24410,24412],{"class":117,"line":149},[115,24407,8618],{"class":262},[115,24409,6600],{"class":132},[115,24411,12350],{"class":202},[115,24413,12353],{"class":132},[115,24415,24416,24418,24420],{"class":117,"line":162},[115,24417,1114],{"class":262},[115,24419,1117],{"class":132},[115,24421,11324],{"class":132},[115,24423,24424,24426,24428,24430],{"class":117,"line":175},[115,24425,1114],{"class":262},[115,24427,1117],{"class":132},[115,24429,1838],{"class":132},[115,24431,1841],{"class":202},[115,24433,24434,24436,24438,24440],{"class":117,"line":350},[115,24435,2001],{"class":262},[115,24437,3480],{"class":132},[115,24439,3483],{"class":132},[115,24441,1987],{"class":132},[16,24443,24444],{},"Reload Caddy only if the proxy config changed:",[106,24446,24448],{"className":108,"code":24447,"language":110,"meta":111,"style":111},"sudo caddy validate --config \u002Fetc\u002Fcaddy\u002FCaddyfile\nsudo systemctl reload caddy\n",[20,24449,24450,24462],{"__ignoreMap":111},[115,24451,24452,24454,24456,24458,24460],{"class":117,"line":118},[115,24453,2001],{"class":262},[115,24455,24106],{"class":132},[115,24457,24109],{"class":132},[115,24459,24112],{"class":202},[115,24461,23945],{"class":132},[115,24463,24464,24466,24468,24470],{"class":117,"line":136},[115,24465,2001],{"class":262},[115,24467,3480],{"class":132},[115,24469,3919],{"class":132},[115,24471,23906],{"class":132},[16,24473,24474],{},"Low-risk pattern:",[63,24476,24477,24480,24483,24486],{},[66,24478,24479],{},"verify locally through Gunicorn first if possible",[66,24481,24482],{},"restart Gunicorn after code, dependency, or migration changes",[66,24484,24485],{},"validate Caddy before every proxy reload",[66,24487,24488,24489,24491,24492,24495],{},"if you want reliable rollbacks, use release directories with a ",[20,24490,13654],{}," symlink instead of deploying with ",[20,24493,24494],{},"git pull"," in place",[52,24497,4319],{"id":4318},[16,24499,24500,24501,24503,24504,24506],{},"Once you repeat this deployment more than a few times, the manual steps become error-prone. Good early automation targets are the environment file install, Gunicorn unit setup, ",[20,24502,10296],{}," plus ",[20,24505,13689],{},", Caddy validation, and post-deploy health checks. A reusable template also helps keep Caddy, Gunicorn, and Django settings consistent across projects.",[11,24508,24509],{"id":19771},"8. Verify the deployment",[16,24511,24512],{},"Application checks:",[63,24514,24515,24518,24520,24522,24527],{},[66,24516,24517],{},"homepage loads over HTTPS",[66,24519,1137],{},[66,24521,15891],{},[66,24523,24524,24525],{},"static assets load with ",[20,24526,17741],{},[66,24528,24529],{},"redirects do not loop",[16,24531,15897],{},[106,24533,24535],{"className":108,"code":24534,"language":110,"meta":111,"style":111},"systemctl status gunicorn\nsystemctl status caddy\njournalctl -u gunicorn -n 50 --no-pager\njournalctl -u caddy -n 50 --no-pager\ncaddy validate --config \u002Fetc\u002Fcaddy\u002FCaddyfile\n",[20,24536,24537,24545,24553,24567,24581],{"__ignoreMap":111},[115,24538,24539,24541,24543],{"class":117,"line":118},[115,24540,1981],{"class":262},[115,24542,1984],{"class":132},[115,24544,1987],{"class":132},[115,24546,24547,24549,24551],{"class":117,"line":136},[115,24548,1981],{"class":262},[115,24550,1984],{"class":132},[115,24552,23906],{"class":132},[115,24554,24555,24557,24559,24561,24563,24565],{"class":117,"line":149},[115,24556,2785],{"class":262},[115,24558,2788],{"class":202},[115,24560,2791],{"class":132},[115,24562,2794],{"class":202},[115,24564,15523],{"class":202},[115,24566,2800],{"class":202},[115,24568,24569,24571,24573,24575,24577,24579],{"class":117,"line":162},[115,24570,2785],{"class":262},[115,24572,2788],{"class":202},[115,24574,24106],{"class":132},[115,24576,2794],{"class":202},[115,24578,15523],{"class":202},[115,24580,2800],{"class":202},[115,24582,24583,24585,24587,24589],{"class":117,"line":175},[115,24584,23953],{"class":262},[115,24586,24109],{"class":132},[115,24588,24112],{"class":202},[115,24590,23945],{"class":132},[16,24592,24593],{},"Header and redirect check:",[106,24595,24596],{"className":108,"code":13228,"language":110,"meta":111,"style":111},[20,24597,24598,24606],{"__ignoreMap":111},[115,24599,24600,24602,24604],{"class":117,"line":118},[115,24601,2764],{"class":262},[115,24603,2767],{"class":202},[115,24605,6494],{"class":132},[115,24607,24608,24610,24612],{"class":117,"line":136},[115,24609,2764],{"class":262},[115,24611,2767],{"class":202},[115,24613,2770],{"class":132},[16,24615,24616],{},"Port exposure check:",[106,24618,24620],{"className":108,"code":24619,"language":110,"meta":111,"style":111},"ss -tulpn | grep -E '(:80|:443|:8000)'\n",[20,24621,24622],{"__ignoreMap":111},[115,24623,24624,24626,24628,24630,24632,24634],{"class":117,"line":118},[115,24625,6472],{"class":262},[115,24627,6475],{"class":202},[115,24629,579],{"class":121},[115,24631,4838],{"class":262},[115,24633,6482],{"class":202},[115,24635,24636],{"class":132}," '(:80|:443|:8000)'\n",[16,24638,23867,24639,3146,24641,24643,24644,24647],{},[20,24640,3808],{},[20,24642,2174],{}," public, and ",[20,24645,24646],{},"8000"," local only.",[23099,24649],{},[11,24651,1321],{"id":1320},[16,24653,11403],{},[63,24655,24656,24661,24667],{},[66,24657,24658,24660],{},[1226,24659,1946],{}," runs the Django WSGI application",[66,24662,24663,24666],{},[1226,24664,24665],{},"Caddy"," handles client connections, TLS, redirects, and reverse proxying",[66,24668,24669,24671],{},[1226,24670,1277],{}," keeps services running and restarts them if they fail",[16,24673,24674],{},"Caddy is a good choice when you want automatic HTTPS with minimal reverse proxy configuration. Compared with more manual setups, it reduces certificate management overhead and usually gives a faster path to a correct HTTPS deployment.",[16,24676,24677,24678,24680],{},"Using a stable shared path for static files avoids tying assets to one code release. That matters if you want cleaner rollbacks with release directories and a ",[20,24679,13654],{}," symlink.",[16,24682,24683,24684,24686],{},"You can use a Unix socket instead of ",[20,24685,20396],{}," if you want a slightly tighter local-only boundary, but localhost TCP is often simpler to debug. If you run multiple Django sites on one server, Caddy can route each domain to a different Gunicorn service with separate site blocks. If you use Django Channels, Caddy can proxy WebSocket traffic too, but you will need an ASGI server and a matching app server design.",[16,24688,24689,24690,24693],{},"Be careful with Let’s Encrypt rate limits during repeated testing. Use correct DNS, open ports ",[20,24691,24692],{},"80\u002F443",", and avoid repeatedly reloading broken configs on a production hostname.",[23099,24695],{},[11,24697,1337],{"id":1336},[63,24699,24700,24706,24712,24718,24724,24730,24736],{},[66,24701,24702,24705],{},[1226,24703,24704],{},"Unix socket instead of TCP",": supported and often cleaner, but requires matching socket permissions between Caddy and Gunicorn.",[66,24707,24708,24711],{},[1226,24709,24710],{},"Multiple sites on one server",": give each app its own systemd service, working directory, environment file, and Caddy site block.",[66,24713,24714,24717],{},[1226,24715,24716],{},"Static files",": local disk is fine for one server; object storage is usually better for shared or scalable deployments.",[66,24719,24720,24723],{},[1226,24721,24722],{},"Media files",": avoid local-only storage if uploads are important and recovery matters.",[66,24725,24726,24729],{},[1226,24727,24728],{},"502 errors after deploy",": usually Gunicorn failed to start, the bind target changed, or Caddy points at the wrong upstream.",[66,24731,24732,24735],{},[1226,24733,24734],{},"Migration failures",": restore from backup if needed; not every schema change is safely reversible.",[66,24737,24738,24741,24742,24744,24745,211],{},[1226,24739,24740],{},"HTTPS issues",": usually DNS mismatch, blocked ",[20,24743,24692],{},", or certificate issuance errors visible in ",[20,24746,24747],{},"journalctl -u caddy",[23099,24749],{},[11,24751,1386],{"id":1385},[16,24753,24754,24755,211],{},"For the Django-side hardening checklist, see ",[1395,24756,3000],{"href":2999},[16,24758,24759,24760,211],{},"If you want a comparable reverse proxy stack, see ",[1395,24761,2986],{"href":2985},[16,24763,24764,24765,211],{},"For a Docker-based production workflow, see ",[1395,24766,2993],{"href":2992},[16,24768,24769,24770,24772,24773,24776],{},"If the proxy is up but requests fail, see ",[1395,24771,8039],{"href":8038}," for an ASGI alternative and ",[1395,24774,24775],{"href":4455},"Fix Django 502 Bad Gateway in production"," if available in your troubleshooting section.",[23099,24778],{},[11,24780,1420],{"id":1419},[52,24782,24784],{"id":24783},"do-i-still-need-gunicorn-if-i-deploy-django-with-caddy","Do I still need Gunicorn if I deploy Django with Caddy?",[16,24786,24787],{},"Yes. Caddy is the reverse proxy and TLS terminator. Django still needs an application server such as Gunicorn to run the Python app.",[52,24789,24791],{"id":24790},"how-do-i-configure-django-so-https-works-correctly-behind-caddy","How do I configure Django so HTTPS works correctly behind Caddy?",[16,24793,8055],{},[106,24795,24797],{"className":2369,"code":24796,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\"]\n",[20,24798,24799,24815,24823,24831],{"__ignoreMap":111},[115,24800,24801,24803,24805,24807,24809,24811,24813],{"class":117,"line":118},[115,24802,2377],{"class":202},[115,24804,2380],{"class":121},[115,24806,2383],{"class":125},[115,24808,2386],{"class":132},[115,24810,1153],{"class":125},[115,24812,2391],{"class":132},[115,24814,2394],{"class":125},[115,24816,24817,24819,24821],{"class":117,"line":136},[115,24818,2417],{"class":202},[115,24820,2380],{"class":121},[115,24822,2412],{"class":202},[115,24824,24825,24827,24829],{"class":117,"line":149},[115,24826,2426],{"class":202},[115,24828,2380],{"class":121},[115,24830,2412],{"class":202},[115,24832,24833,24835,24837,24839,24841],{"class":117,"line":162},[115,24834,2725],{"class":202},[115,24836,2380],{"class":121},[115,24838,7493],{"class":125},[115,24840,15110],{"class":132},[115,24842,2552],{"class":125},[16,24844,24845,24846,24848],{},"Also make sure ",[20,24847,2719],{}," includes the production domain, and keep Gunicorn reachable only from localhost or a Unix socket.",[52,24850,24852],{"id":24851},"should-caddy-serve-django-static-files-or-should-i-use-object-storage","Should Caddy serve Django static files or should I use object storage?",[16,24854,24855],{},"For a single server, Caddy serving collected static files from disk is simple and works well. For multi-server deployments or heavier traffic, object storage is usually easier to scale and recover.",[52,24857,24859],{"id":24858},"why-is-caddy-not-issuing-a-certificate-for-my-django-site","Why is Caddy not issuing a certificate for my Django site?",[16,24861,24862],{},"Usually one of these is wrong:",[63,24864,24865,24868,24875,24878],{},[66,24866,24867],{},"domain DNS does not point to the server",[66,24869,24169,24870,4493,24872,24874],{},[20,24871,3808],{},[20,24873,2174],{}," are blocked",[66,24876,24877],{},"the server is behind another proxy that interferes with validation",[66,24879,24880],{},"the hostname in the Caddyfile does not match the public domain",[16,24882,5438],{},[106,24884,24885],{"className":108,"code":24138,"language":110,"meta":111,"style":111},[20,24886,24887],{"__ignoreMap":111},[115,24888,24889,24891,24893,24895,24897,24899],{"class":117,"line":118},[115,24890,2785],{"class":262},[115,24892,2788],{"class":202},[115,24894,24106],{"class":132},[115,24896,2794],{"class":202},[115,24898,2797],{"class":202},[115,24900,2800],{"class":202},[52,24902,24904],{"id":24903},"how-do-i-roll-back-safely-if-a-django-deploy-behind-caddy-fails","How do I roll back safely if a Django deploy behind Caddy fails?",[16,24906,24907],{},"If the problem is app code, restore the previous release and restart Gunicorn. If the problem is only proxy config, restore the previous Caddyfile, validate it, and reload Caddy without changing the app service. For risky migrations, take a database backup before deployment.",[1485,24909,24910],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":111,"searchDepth":149,"depth":149,"links":24912},[24913,24914,24915,24916,24917,24918,24919,24920,24921,24922,24925,24926,24927,24928,24929],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":23105,"depth":136,"text":23106},{"id":23177,"depth":136,"text":23178},{"id":23490,"depth":136,"text":23491},{"id":23876,"depth":136,"text":23877},{"id":24229,"depth":136,"text":24230},{"id":24275,"depth":136,"text":24276},{"id":24382,"depth":136,"text":24383,"children":24923},[24924],{"id":4318,"depth":149,"text":4319},{"id":19771,"depth":136,"text":24509},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":24930},[24931,24932,24933,24934,24935],{"id":24783,"depth":149,"text":24784},{"id":24790,"depth":149,"text":24791},{"id":24851,"depth":149,"text":24852},{"id":24858,"depth":149,"text":24859},{"id":24903,"depth":149,"text":24904},"If you want to deploy Django with Caddy in production, the main challenge is not just putting a reverse proxy in front of the app.",{},"\u002Fdeploy-django-with-caddy-https","6",[2985,14027,23041],{"title":8046,"description":24936},[1557,23953,14954],"deploy-django-with-caddy-https",[1557,23953,14954],"3pQNFLOMBDckJhLugqhBxv8GfRVLI3eFsihnTURz59A",{"id":24947,"title":2993,"body":24948,"category":3088,"description":27127,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":27128,"navigation":309,"path":27129,"priority":27130,"related":27131,"role":1553,"section":3098,"seo":27132,"stack":27133,"stem":27134,"tags":27135,"type":1561,"__hash__":27136},"articles\u002Fdeploy-django-docker-compose-production.md",{"type":8,"value":24949,"toc":27096},[24950,24952,24959,24962,24984,24987,24989,24992,25024,25027,25059,25061,25065,25069,25072,25106,25109,25129,25132,25136,25139,25339,25342,25371,25377,25427,25430,25439,25442,25473,25475,25488,25494,25498,25501,25668,25671,25688,25691,25709,25718,25722,25725,26000,26003,26030,26040,26042,26055,26058,26062,26066,26071,26268,26271,26275,26282,26286,26289,26292,26315,26318,26343,26358,26367,26385,26387,26417,26420,26424,26427,26508,26511,26546,26549,26567,26570,26638,26641,26654,26657,26677,26684,26686,26688,26733,26736,26756,26759,26779,26783,26788,26818,26824,26844,26850,26880,26883,26915,26918,26920,26923,26943,26946,26960,26963,26967,26973,26975,27027,27029,27034,27039,27044,27049,27051,27055,27058,27062,27065,27069,27072,27076,27079,27083,27093],[11,24951,14],{"id":13},[16,24953,24954,24955,24958],{},"Many Docker Compose examples for Django are built for local development, not production. They expose the app server directly, run with ",[20,24956,24957],{},"DEBUG=True",", commit secrets to version control, skip TLS, and provide no safe process for migrations or rollback.",[16,24960,24961],{},"A production-safe Django Docker Compose setup needs a few clear boundaries:",[63,24963,24964,24967,24972,24975,24978,24981],{},[66,24965,24966],{},"Django runs behind Gunicorn",[66,24968,24969,24970],{},"only the reverse proxy exposes ports ",[20,24971,24692],{},[66,24973,24974],{},"PostgreSQL and Redis stay on the internal Docker network",[66,24976,24977],{},"secrets are injected at runtime, not baked into the image",[66,24979,24980],{},"migrations and static files are handled deliberately",[66,24982,24983],{},"the previous image is kept for rollback",[16,24985,24986],{},"If you want to deploy Django with Docker Compose on a single VPS or small Linux server, Compose can work well, but only if you use it as a real release system rather than a development shortcut.",[11,24988,30],{"id":29},[16,24990,24991],{},"A safe Django production Docker Compose setup on one host usually looks like this:",[63,24993,24994,24999,25006,25011,25016,25021],{},[66,24995,24996,24998],{},[20,24997,20417],{},": Django app running with Gunicorn",[66,25000,25001,4493,25003,25005],{},[20,25002,2156],{},[20,25004,23953],{},": reverse proxy and TLS termination",[66,25007,25008,25010],{},[20,25009,20420],{},": PostgreSQL with a named volume",[66,25012,25013,25015],{},[20,25014,6336],{},": optional, if your app actually uses cache, Celery, or channels",[66,25017,25018,25020],{},[20,25019,191],{},": runtime secrets and environment variables stored on the server",[66,25022,25023],{},"named volumes for database, media, and static persistence where needed",[16,25025,25026],{},"A practical release flow is:",[1173,25028,25029,25032,25037,25040,25043,25047,25053,25056],{},[66,25030,25031],{},"build or pull a tagged image",[66,25033,25034,25035],{},"upload or update the production ",[20,25036,191],{},[66,25038,25039],{},"start the database if needed",[66,25041,25042],{},"run database migrations as an explicit one-off command",[66,25044,7902,25045],{},[20,25046,13689],{},[66,25048,25049,25050],{},"start or update services with ",[20,25051,25052],{},"docker compose up -d",[66,25054,25055],{},"verify containers, logs, HTTPS, and static files",[66,25057,25058],{},"keep the previous image tag for rollback",[11,25060,43],{"id":42},[11,25062,25064],{"id":25063},"_1-define-the-production-architecture","1. Define the production architecture",[52,25066,25068],{"id":25067},"minimum-compose-stack-for-django-in-production","Minimum Compose stack for Django in production",[16,25070,25071],{},"Use these services:",[63,25073,25074,25079,25084,25089,25094],{},[66,25075,25076,25078],{},[20,25077,20417],{},": Django + Gunicorn",[66,25080,25081,25083],{},[20,25082,2156],{},": reverse proxy",[66,25085,25086,25088],{},[20,25087,20420],{},": PostgreSQL",[66,25090,25091,25093],{},[20,25092,6336],{},": only if your project needs it",[66,25095,25096,25097,1153,25100,20346,25103],{},"named volumes for ",[20,25098,25099],{},"postgres_data",[20,25101,25102],{},"media_data",[20,25104,25105],{},"static_data",[16,25107,25108],{},"Public exposure rules:",[63,25110,25111,25123,25126],{},[66,25112,25113,25114,25116,25117,3146,25120],{},"expose only ",[20,25115,2156],{}," on ",[20,25118,25119],{},"80:80",[20,25121,25122],{},"443:443",[66,25124,25125],{},"do not publish PostgreSQL, Redis, or Gunicorn ports",[66,25127,25128],{},"let Docker networking handle service-to-service traffic",[16,25130,25131],{},"This is the main difference between a development Compose file and a production one: internal services stay private.",[11,25133,25135],{"id":25134},"_2-prepare-django-settings-for-production","2. Prepare Django settings for production",[16,25137,25138],{},"In your production settings, read values from environment variables and enable HTTPS-aware settings:",[106,25140,25142],{"className":2369,"code":25141,"language":1114,"meta":111,"style":111},"import os\n\nDEBUG = os.environ.get(\"DJANGO_DEBUG\", \"False\").lower() == \"true\"\n\nALLOWED_HOSTS = [h for h in os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"\").split(\",\") if h]\nCSRF_TRUSTED_ORIGINS = [o for o in os.environ.get(\"DJANGO_CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if o]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_SSL_REDIRECT = True\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = \"\u002Fapp\u002Fstaticfiles\"\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = \"\u002Fapp\u002Fmedia\"\n",[20,25143,25144,25150,25154,25179,25183,25218,25253,25257,25273,25281,25289,25297,25301,25309,25318,25322,25330],{"__ignoreMap":111},[115,25145,25146,25148],{"class":117,"line":118},[115,25147,5613],{"class":121},[115,25149,5616],{"class":125},[115,25151,25152],{"class":117,"line":136},[115,25153,310],{"emptyLinePlaceholder":309},[115,25155,25156,25158,25160,25162,25165,25167,25170,25173,25176],{"class":117,"line":149},[115,25157,7350],{"class":202},[115,25159,2380],{"class":121},[115,25161,8884],{"class":125},[115,25163,25164],{"class":132},"\"DJANGO_DEBUG\"",[115,25166,1153],{"class":125},[115,25168,25169],{"class":132},"\"False\"",[115,25171,25172],{"class":125},").lower() ",[115,25174,25175],{"class":121},"==",[115,25177,25178],{"class":132}," \"true\"\n",[115,25180,25181],{"class":117,"line":162},[115,25182,310],{"emptyLinePlaceholder":309},[115,25184,25185,25187,25189,25192,25194,25196,25198,25200,25203,25205,25207,25209,25211,25213,25215],{"class":117,"line":175},[115,25186,2719],{"class":202},[115,25188,2380],{"class":121},[115,25190,25191],{"class":125}," [h ",[115,25193,18256],{"class":121},[115,25195,18259],{"class":125},[115,25197,18262],{"class":121},[115,25199,8884],{"class":125},[115,25201,25202],{"class":132},"\"DJANGO_ALLOWED_HOSTS\"",[115,25204,1153],{"class":125},[115,25206,18272],{"class":132},[115,25208,18275],{"class":125},[115,25210,18278],{"class":132},[115,25212,18281],{"class":125},[115,25214,10833],{"class":121},[115,25216,25217],{"class":125}," h]\n",[115,25219,25220,25222,25224,25227,25229,25231,25233,25235,25238,25240,25242,25244,25246,25248,25250],{"class":117,"line":350},[115,25221,2725],{"class":202},[115,25223,2380],{"class":121},[115,25225,25226],{"class":125}," [o ",[115,25228,18256],{"class":121},[115,25230,18300],{"class":125},[115,25232,18262],{"class":121},[115,25234,8884],{"class":125},[115,25236,25237],{"class":132},"\"DJANGO_CSRF_TRUSTED_ORIGINS\"",[115,25239,1153],{"class":125},[115,25241,18272],{"class":132},[115,25243,18275],{"class":125},[115,25245,18278],{"class":132},[115,25247,18281],{"class":125},[115,25249,10833],{"class":121},[115,25251,25252],{"class":125}," o]\n",[115,25254,25255],{"class":117,"line":365},[115,25256,310],{"emptyLinePlaceholder":309},[115,25258,25259,25261,25263,25265,25267,25269,25271],{"class":117,"line":380},[115,25260,2377],{"class":202},[115,25262,2380],{"class":121},[115,25264,2383],{"class":125},[115,25266,2386],{"class":132},[115,25268,1153],{"class":125},[115,25270,2391],{"class":132},[115,25272,2394],{"class":125},[115,25274,25275,25277,25279],{"class":117,"line":487},[115,25276,2417],{"class":202},[115,25278,2380],{"class":121},[115,25280,2412],{"class":202},[115,25282,25283,25285,25287],{"class":117,"line":2095},[115,25284,2426],{"class":202},[115,25286,2380],{"class":121},[115,25288,2412],{"class":202},[115,25290,25291,25293,25295],{"class":117,"line":2104},[115,25292,2407],{"class":202},[115,25294,2380],{"class":121},[115,25296,2412],{"class":202},[115,25298,25299],{"class":117,"line":2113},[115,25300,310],{"emptyLinePlaceholder":309},[115,25302,25303,25305,25307],{"class":117,"line":2122},[115,25304,11908],{"class":202},[115,25306,2380],{"class":121},[115,25308,11913],{"class":132},[115,25310,25311,25313,25315],{"class":117,"line":2131},[115,25312,11918],{"class":202},[115,25314,2380],{"class":121},[115,25316,25317],{"class":132}," \"\u002Fapp\u002Fstaticfiles\"\n",[115,25319,25320],{"class":117,"line":2136},[115,25321,310],{"emptyLinePlaceholder":309},[115,25323,25324,25326,25328],{"class":117,"line":2142},[115,25325,15204],{"class":202},[115,25327,2380],{"class":121},[115,25329,15209],{"class":132},[115,25331,25332,25334,25336],{"class":117,"line":2273},[115,25333,15214],{"class":202},[115,25335,2380],{"class":121},[115,25337,25338],{"class":132}," \"\u002Fapp\u002Fmedia\"\n",[16,25340,25341],{},"If you want HSTS, enable it only after HTTPS is working correctly:",[106,25343,25345],{"className":2369,"code":25344,"language":1114,"meta":111,"style":111},"SECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n",[20,25346,25347,25355,25363],{"__ignoreMap":111},[115,25348,25349,25351,25353],{"class":117,"line":118},[115,25350,7440],{"class":202},[115,25352,2380],{"class":121},[115,25354,11991],{"class":202},[115,25356,25357,25359,25361],{"class":117,"line":136},[115,25358,7464],{"class":202},[115,25360,2380],{"class":121},[115,25362,2412],{"class":202},[115,25364,25365,25367,25369],{"class":117,"line":149},[115,25366,12004],{"class":202},[115,25368,2380],{"class":121},[115,25370,7355],{"class":202},[16,25372,25373,25374,25376],{},"Store secrets in a server-side ",[20,25375,191],{}," file, not in Git:",[106,25378,25380],{"className":2329,"code":25379,"language":2331,"meta":111,"style":111},"DJANGO_SECRET_KEY=replace-me\nDJANGO_DEBUG=False\nDJANGO_ALLOWED_HOSTS=example.com,www.example.com\nDJANGO_CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n\nPOSTGRES_DB=app\nPOSTGRES_USER=app\nPOSTGRES_PASSWORD=strong-password\nPOSTGRES_HOST=db\nPOSTGRES_PORT=5432\n",[20,25381,25382,25386,25390,25394,25398,25402,25407,25412,25417,25422],{"__ignoreMap":111},[115,25383,25384],{"class":117,"line":118},[115,25385,12429],{},[115,25387,25388],{"class":117,"line":136},[115,25389,15015],{},[115,25391,25392],{"class":117,"line":149},[115,25393,15020],{},[115,25395,25396],{"class":117,"line":162},[115,25397,15025],{},[115,25399,25400],{"class":117,"line":175},[115,25401,310],{"emptyLinePlaceholder":309},[115,25403,25404],{"class":117,"line":350},[115,25405,25406],{},"POSTGRES_DB=app\n",[115,25408,25409],{"class":117,"line":365},[115,25410,25411],{},"POSTGRES_USER=app\n",[115,25413,25414],{"class":117,"line":380},[115,25415,25416],{},"POSTGRES_PASSWORD=strong-password\n",[115,25418,25419],{"class":117,"line":487},[115,25420,25421],{},"POSTGRES_HOST=db\n",[115,25423,25424],{"class":117,"line":2095},[115,25425,25426],{},"POSTGRES_PORT=5432\n",[16,25428,25429],{},"If your app uses Redis, add it too:",[106,25431,25433],{"className":2329,"code":25432,"language":2331,"meta":111,"style":111},"REDIS_URL=redis:\u002F\u002Fredis:6379\u002F0\n",[20,25434,25435],{"__ignoreMap":111},[115,25436,25437],{"class":117,"line":118},[115,25438,25432],{},[16,25440,25441],{},"Copy the file to the server and lock permissions:",[106,25443,25445],{"className":108,"code":25444,"language":110,"meta":111,"style":111},"mkdir -p \u002Fsrv\u002Fmyapp\ncp .env \u002Fsrv\u002Fmyapp\u002F.env\nchmod 600 \u002Fsrv\u002Fmyapp\u002F.env\n",[20,25446,25447,25455,25465],{"__ignoreMap":111},[115,25448,25449,25451,25453],{"class":117,"line":118},[115,25450,12314],{"class":262},[115,25452,1001],{"class":202},[115,25454,14799],{"class":132},[115,25456,25457,25460,25463],{"class":117,"line":136},[115,25458,25459],{"class":262},"cp",[115,25461,25462],{"class":132}," .env",[115,25464,214],{"class":132},[115,25466,25467,25469,25471],{"class":117,"line":149},[115,25468,263],{"class":262},[115,25470,266],{"class":202},[115,25472,214],{"class":132},[16,25474,8572],{},[106,25476,25478],{"className":108,"code":25477,"language":110,"meta":111,"style":111},"ls -l \u002Fsrv\u002Fmyapp\u002F.env\n",[20,25479,25480],{"__ignoreMap":111},[115,25481,25482,25484,25486],{"class":117,"line":118},[115,25483,532],{"class":262},[115,25485,14881],{"class":202},[115,25487,214],{"class":132},[16,25489,25490,25491,25493],{},"Rollback note: enabling ",[20,25492,2407],{}," before proxy headers are correct can cause redirect loops or login issues. Verify proxy behavior first.",[11,25495,25497],{"id":25496},"_3-write-the-production-dockerfile","3. Write the production Dockerfile",[16,25499,25500],{},"Use a slim base image and keep runtime behavior predictable:",[106,25502,25504],{"className":16832,"code":25503,"language":16834,"meta":111,"style":111},"FROM python:3.12-slim\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nWORKDIR \u002Fapp\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    build-essential libpq-dev \\\n    && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nRUN useradd -m appuser && chown -R appuser:appuser \u002Fapp\nUSER appuser\n\nCMD [\"gunicorn\", \"myproject.wsgi:application\", \"--bind\", \"0.0.0.0:8000\", \"--workers\", \"3\", \"--timeout\", \"60\", \"--access-logfile\", \"-\", \"--error-logfile\", \"-\"]\n",[20,25505,25506,25512,25516,25522,25528,25532,25538,25542,25549,25554,25559,25563,25569,25575,25579,25585,25589,25596,25604,25608],{"__ignoreMap":111},[115,25507,25508,25510],{"class":117,"line":118},[115,25509,11089],{"class":121},[115,25511,16843],{"class":125},[115,25513,25514],{"class":117,"line":136},[115,25515,310],{"emptyLinePlaceholder":309},[115,25517,25518,25520],{"class":117,"line":149},[115,25519,16852],{"class":121},[115,25521,16855],{"class":125},[115,25523,25524,25526],{"class":117,"line":162},[115,25525,16852],{"class":121},[115,25527,16862],{"class":125},[115,25529,25530],{"class":117,"line":175},[115,25531,310],{"emptyLinePlaceholder":309},[115,25533,25534,25536],{"class":117,"line":350},[115,25535,16885],{"class":121},[115,25537,16888],{"class":125},[115,25539,25540],{"class":117,"line":365},[115,25541,310],{"emptyLinePlaceholder":309},[115,25543,25544,25546],{"class":117,"line":380},[115,25545,16905],{"class":121},[115,25547,25548],{"class":125}," apt-get update && apt-get install -y --no-install-recommends \\\n",[115,25550,25551],{"class":117,"line":487},[115,25552,25553],{"class":125},"    build-essential libpq-dev \\\n",[115,25555,25556],{"class":117,"line":2095},[115,25557,25558],{"class":125},"    && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n",[115,25560,25561],{"class":117,"line":2104},[115,25562,310],{"emptyLinePlaceholder":309},[115,25564,25565,25567],{"class":117,"line":2113},[115,25566,16897],{"class":121},[115,25568,16900],{"class":125},[115,25570,25571,25573],{"class":117,"line":2122},[115,25572,16905],{"class":121},[115,25574,16908],{"class":125},[115,25576,25577],{"class":117,"line":2131},[115,25578,310],{"emptyLinePlaceholder":309},[115,25580,25581,25583],{"class":117,"line":2136},[115,25582,16897],{"class":121},[115,25584,16919],{"class":125},[115,25586,25587],{"class":117,"line":2142},[115,25588,310],{"emptyLinePlaceholder":309},[115,25590,25591,25593],{"class":117,"line":2273},[115,25592,16905],{"class":121},[115,25594,25595],{"class":125}," useradd -m appuser && chown -R appuser:appuser \u002Fapp\n",[115,25597,25598,25601],{"class":117,"line":2282},[115,25599,25600],{"class":121},"USER",[115,25602,25603],{"class":125}," appuser\n",[115,25605,25606],{"class":117,"line":2291},[115,25607,310],{"emptyLinePlaceholder":309},[115,25609,25610,25612,25614,25616,25618,25621,25623,25625,25627,25629,25631,25633,25635,25638,25640,25643,25645,25647,25649,25652,25654,25657,25659,25662,25664,25666],{"class":117,"line":2299},[115,25611,16939],{"class":121},[115,25613,7493],{"class":125},[115,25615,16944],{"class":132},[115,25617,1153],{"class":125},[115,25619,25620],{"class":132},"\"myproject.wsgi:application\"",[115,25622,1153],{"class":125},[115,25624,16949],{"class":132},[115,25626,1153],{"class":125},[115,25628,16954],{"class":132},[115,25630,1153],{"class":125},[115,25632,16959],{"class":132},[115,25634,1153],{"class":125},[115,25636,25637],{"class":132},"\"3\"",[115,25639,1153],{"class":125},[115,25641,25642],{"class":132},"\"--timeout\"",[115,25644,1153],{"class":125},[115,25646,10767],{"class":132},[115,25648,1153],{"class":125},[115,25650,25651],{"class":132},"\"--access-logfile\"",[115,25653,1153],{"class":125},[115,25655,25656],{"class":132},"\"-\"",[115,25658,1153],{"class":125},[115,25660,25661],{"class":132},"\"--error-logfile\"",[115,25663,1153],{"class":125},[115,25665,25656],{"class":132},[115,25667,2552],{"class":125},[16,25669,25670],{},"This gives you a clean baseline:",[63,25672,25673,25676,25682,25685],{},[66,25674,25675],{},"Gunicorn is the app server",[66,25677,25678,25679],{},"logs go to stdout\u002Fstderr for ",[20,25680,25681],{},"docker compose logs",[66,25683,25684],{},"the process runs as a non-root user",[66,25686,25687],{},"secrets are not embedded in the image",[16,25689,25690],{},"Build locally to verify:",[106,25692,25694],{"className":108,"code":25693,"language":110,"meta":111,"style":111},"docker build -t myapp:latest .\n",[20,25695,25696],{"__ignoreMap":111},[115,25697,25698,25700,25702,25704,25707],{"class":117,"line":118},[115,25699,3295],{"class":262},[115,25701,17022],{"class":132},[115,25703,3909],{"class":202},[115,25705,25706],{"class":132}," myapp:latest",[115,25708,17030],{"class":132},[16,25710,25711,25712,25715,25716,211],{},"For repeatable releases, prefer immutable tags such as ",[20,25713,25714],{},"myapp:2026-04-24"," rather than relying only on ",[20,25717,19147],{},[11,25719,25721],{"id":25720},"_4-create-a-production-compose-file","4. Create a production compose file",[16,25723,25724],{},"For production, it is clearer to reference a tagged image explicitly. If you build on the server, do it before deployment and update the image tag intentionally.",[106,25726,25728],{"className":2485,"code":25727,"language":2487,"meta":111,"style":111},"services:\n  web:\n    image: myapp:2026-04-24\n    env_file:\n      - \u002Fsrv\u002Fmyapp\u002F.env\n    depends_on:\n      - db\n    restart: unless-stopped\n    volumes:\n      - media_data:\u002Fapp\u002Fmedia\n      - static_data:\u002Fapp\u002Fstaticfiles\n    expose:\n      - \"8000\"\n\n  db:\n    image: postgres:16\n    env_file:\n      - \u002Fsrv\u002Fmyapp\u002F.env\n    restart: unless-stopped\n    volumes:\n      - postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n\n  nginx:\n    image: nginx:1.27-alpine\n    depends_on:\n      - web\n    restart: unless-stopped\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - .\u002Fnginx\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n      - static_data:\u002Fvar\u002Fwww\u002Fstatic:ro\n      - media_data:\u002Fvar\u002Fwww\u002Fmedia:ro\n      - .\u002Fcerts:\u002Fetc\u002Fnginx\u002Fcerts:ro\n\nvolumes:\n  postgres_data:\n  media_data:\n  static_data:\n",[20,25729,25730,25736,25742,25751,25757,25764,25770,25777,25786,25792,25799,25806,25813,25820,25824,25831,25840,25846,25852,25860,25866,25873,25877,25884,25893,25899,25905,25913,25920,25927,25934,25940,25947,25954,25961,25968,25972,25979,25986,25993],{"__ignoreMap":111},[115,25731,25732,25734],{"class":117,"line":118},[115,25733,2495],{"class":2494},[115,25735,2498],{"class":125},[115,25737,25738,25740],{"class":117,"line":136},[115,25739,2503],{"class":2494},[115,25741,2498],{"class":125},[115,25743,25744,25746,25748],{"class":117,"line":149},[115,25745,2510],{"class":2494},[115,25747,2513],{"class":125},[115,25749,25750],{"class":132},"myapp:2026-04-24\n",[115,25752,25753,25755],{"class":117,"line":162},[115,25754,2521],{"class":2494},[115,25756,2498],{"class":125},[115,25758,25759,25761],{"class":117,"line":175},[115,25760,5976],{"class":125},[115,25762,25763],{"class":132},"\u002Fsrv\u002Fmyapp\u002F.env\n",[115,25765,25766,25768],{"class":117,"line":350},[115,25767,9634],{"class":2494},[115,25769,2498],{"class":125},[115,25771,25772,25774],{"class":117,"line":365},[115,25773,5976],{"class":125},[115,25775,25776],{"class":132},"db\n",[115,25778,25779,25781,25783],{"class":117,"line":380},[115,25780,5960],{"class":2494},[115,25782,2513],{"class":125},[115,25784,25785],{"class":132},"unless-stopped\n",[115,25787,25788,25790],{"class":117,"line":487},[115,25789,9733],{"class":2494},[115,25791,2498],{"class":125},[115,25793,25794,25796],{"class":117,"line":2095},[115,25795,5976],{"class":125},[115,25797,25798],{"class":132},"media_data:\u002Fapp\u002Fmedia\n",[115,25800,25801,25803],{"class":117,"line":2104},[115,25802,5976],{"class":125},[115,25804,25805],{"class":132},"static_data:\u002Fapp\u002Fstaticfiles\n",[115,25807,25808,25811],{"class":117,"line":2113},[115,25809,25810],{"class":2494},"    expose",[115,25812,2498],{"class":125},[115,25814,25815,25817],{"class":117,"line":2122},[115,25816,5976],{"class":125},[115,25818,25819],{"class":132},"\"8000\"\n",[115,25821,25822],{"class":117,"line":2131},[115,25823,310],{"emptyLinePlaceholder":309},[115,25825,25826,25829],{"class":117,"line":2136},[115,25827,25828],{"class":2494},"  db",[115,25830,2498],{"class":125},[115,25832,25833,25835,25837],{"class":117,"line":2142},[115,25834,2510],{"class":2494},[115,25836,2513],{"class":125},[115,25838,25839],{"class":132},"postgres:16\n",[115,25841,25842,25844],{"class":117,"line":2273},[115,25843,2521],{"class":2494},[115,25845,2498],{"class":125},[115,25847,25848,25850],{"class":117,"line":2282},[115,25849,5976],{"class":125},[115,25851,25763],{"class":132},[115,25853,25854,25856,25858],{"class":117,"line":2291},[115,25855,5960],{"class":2494},[115,25857,2513],{"class":125},[115,25859,25785],{"class":132},[115,25861,25862,25864],{"class":117,"line":2299},[115,25863,9733],{"class":2494},[115,25865,2498],{"class":125},[115,25867,25868,25870],{"class":117,"line":2307},[115,25869,5976],{"class":125},[115,25871,25872],{"class":132},"postgres_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[115,25874,25875],{"class":117,"line":2315},[115,25876,310],{"emptyLinePlaceholder":309},[115,25878,25879,25882],{"class":117,"line":2320},[115,25880,25881],{"class":2494},"  nginx",[115,25883,2498],{"class":125},[115,25885,25886,25888,25890],{"class":117,"line":7083},[115,25887,2510],{"class":2494},[115,25889,2513],{"class":125},[115,25891,25892],{"class":132},"nginx:1.27-alpine\n",[115,25894,25895,25897],{"class":117,"line":7090},[115,25896,9634],{"class":2494},[115,25898,2498],{"class":125},[115,25900,25901,25903],{"class":117,"line":7097},[115,25902,5976],{"class":125},[115,25904,20795],{"class":132},[115,25906,25907,25909,25911],{"class":117,"line":7108},[115,25908,5960],{"class":2494},[115,25910,2513],{"class":125},[115,25912,25785],{"class":132},[115,25914,25915,25918],{"class":117,"line":7113},[115,25916,25917],{"class":2494},"    ports",[115,25919,2498],{"class":125},[115,25921,25922,25924],{"class":117,"line":16535},[115,25923,5976],{"class":125},[115,25925,25926],{"class":132},"\"80:80\"\n",[115,25928,25929,25931],{"class":117,"line":16544},[115,25930,5976],{"class":125},[115,25932,25933],{"class":132},"\"443:443\"\n",[115,25935,25936,25938],{"class":117,"line":16549},[115,25937,9733],{"class":2494},[115,25939,2498],{"class":125},[115,25941,25942,25944],{"class":117,"line":16555},[115,25943,5976],{"class":125},[115,25945,25946],{"class":132},".\u002Fnginx\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n",[115,25948,25949,25951],{"class":117,"line":16564},[115,25950,5976],{"class":125},[115,25952,25953],{"class":132},"static_data:\u002Fvar\u002Fwww\u002Fstatic:ro\n",[115,25955,25956,25958],{"class":117,"line":16573},[115,25957,5976],{"class":125},[115,25959,25960],{"class":132},"media_data:\u002Fvar\u002Fwww\u002Fmedia:ro\n",[115,25962,25963,25965],{"class":117,"line":16582},[115,25964,5976],{"class":125},[115,25966,25967],{"class":132},".\u002Fcerts:\u002Fetc\u002Fnginx\u002Fcerts:ro\n",[115,25969,25970],{"class":117,"line":16587},[115,25971,310],{"emptyLinePlaceholder":309},[115,25973,25974,25977],{"class":117,"line":16596},[115,25975,25976],{"class":2494},"volumes",[115,25978,2498],{"class":125},[115,25980,25981,25984],{"class":117,"line":16609},[115,25982,25983],{"class":2494},"  postgres_data",[115,25985,2498],{"class":125},[115,25987,25988,25991],{"class":117,"line":16614},[115,25989,25990],{"class":2494},"  media_data",[115,25992,2498],{"class":125},[115,25994,25995,25998],{"class":117,"line":16624},[115,25996,25997],{"class":2494},"  static_data",[115,25999,2498],{"class":125},[16,26001,26002],{},"If your project uses Redis, add it:",[106,26004,26006],{"className":2485,"code":26005,"language":2487,"meta":111,"style":111},"  redis:\n    image: redis:7\n    restart: unless-stopped\n",[20,26007,26008,26014,26022],{"__ignoreMap":111},[115,26009,26010,26012],{"class":117,"line":118},[115,26011,2598],{"class":2494},[115,26013,2498],{"class":125},[115,26015,26016,26018,26020],{"class":117,"line":136},[115,26017,2510],{"class":2494},[115,26019,2513],{"class":125},[115,26021,2609],{"class":132},[115,26023,26024,26026,26028],{"class":117,"line":149},[115,26025,5960],{"class":2494},[115,26027,2513],{"class":125},[115,26029,25785],{"class":132},[16,26031,26032,26033,26035,26036,26039],{},"And then add ",[20,26034,6336],{}," back to ",[20,26037,26038],{},"web.depends_on"," and your application settings.",[16,26041,8508],{},[63,26043,26044,26049,26052],{},[66,26045,26046,26048],{},[20,26047,9800],{}," does not guarantee that PostgreSQL is ready to accept connections",[66,26050,26051],{},"do not mount your full source tree into the production container",[66,26053,26054],{},"pin major service versions where practical",[16,26056,26057],{},"Backup note: a persistent volume is not a backup strategy.",[11,26059,26061],{"id":26060},"_5-configure-gunicorn-and-nginx","5. Configure Gunicorn and Nginx",[52,26063,26065],{"id":26064},"nginx-config","Nginx config",[16,26067,8628,26068,241],{},[20,26069,26070],{},"nginx\u002Fdefault.conf",[106,26072,26074],{"className":2154,"code":26073,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fnginx\u002Fcerts\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fnginx\u002Fcerts\u002Fprivkey.pem;\n\n    client_max_body_size 10m;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fvar\u002Fwww\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fweb:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_read_timeout 60;\n    }\n}\n",[20,26075,26076,26082,26090,26096,26104,26108,26112,26118,26126,26132,26136,26143,26150,26154,26163,26167,26175,26182,26186,26190,26198,26205,26209,26213,26221,26228,26234,26240,26246,26252,26260,26264],{"__ignoreMap":111},[115,26077,26078,26080],{"class":117,"line":118},[115,26079,2163],{"class":121},[115,26081,2166],{"class":125},[115,26083,26084,26086,26088],{"class":117,"line":136},[115,26085,2171],{"class":121},[115,26087,3808],{"class":202},[115,26089,3811],{"class":125},[115,26091,26092,26094],{"class":117,"line":149},[115,26093,2182],{"class":121},[115,26095,3713],{"class":125},[115,26097,26098,26100,26102],{"class":117,"line":162},[115,26099,3822],{"class":121},[115,26101,3825],{"class":202},[115,26103,3828],{"class":125},[115,26105,26106],{"class":117,"line":175},[115,26107,2323],{"class":125},[115,26109,26110],{"class":117,"line":350},[115,26111,310],{"emptyLinePlaceholder":309},[115,26113,26114,26116],{"class":117,"line":365},[115,26115,2163],{"class":121},[115,26117,2166],{"class":125},[115,26119,26120,26122,26124],{"class":117,"line":380},[115,26121,2171],{"class":121},[115,26123,2174],{"class":202},[115,26125,2177],{"class":125},[115,26127,26128,26130],{"class":117,"line":487},[115,26129,2182],{"class":121},[115,26131,3713],{"class":125},[115,26133,26134],{"class":117,"line":2095},[115,26135,310],{"emptyLinePlaceholder":309},[115,26137,26138,26140],{"class":117,"line":2104},[115,26139,2194],{"class":121},[115,26141,26142],{"class":125},"\u002Fetc\u002Fnginx\u002Fcerts\u002Ffullchain.pem;\n",[115,26144,26145,26147],{"class":117,"line":2113},[115,26146,2202],{"class":121},[115,26148,26149],{"class":125},"\u002Fetc\u002Fnginx\u002Fcerts\u002Fprivkey.pem;\n",[115,26151,26152],{"class":117,"line":2122},[115,26153,310],{"emptyLinePlaceholder":309},[115,26155,26156,26158,26161],{"class":117,"line":2131},[115,26157,6987],{"class":121},[115,26159,26160],{"class":202},"10m",[115,26162,3811],{"class":125},[115,26164,26165],{"class":117,"line":2136},[115,26166,310],{"emptyLinePlaceholder":309},[115,26168,26169,26171,26173],{"class":117,"line":2142},[115,26170,2214],{"class":121},[115,26172,2217],{"class":262},[115,26174,2220],{"class":125},[115,26176,26177,26179],{"class":117,"line":2273},[115,26178,2225],{"class":121},[115,26180,26181],{"class":125},"\u002Fvar\u002Fwww\u002Fstatic\u002F;\n",[115,26183,26184],{"class":117,"line":2282},[115,26185,2233],{"class":125},[115,26187,26188],{"class":117,"line":2291},[115,26189,310],{"emptyLinePlaceholder":309},[115,26191,26192,26194,26196],{"class":117,"line":2299},[115,26193,2214],{"class":121},[115,26195,2244],{"class":262},[115,26197,2220],{"class":125},[115,26199,26200,26202],{"class":117,"line":2307},[115,26201,2225],{"class":121},[115,26203,26204],{"class":125},"\u002Fvar\u002Fwww\u002Fmedia\u002F;\n",[115,26206,26207],{"class":117,"line":2315},[115,26208,2233],{"class":125},[115,26210,26211],{"class":117,"line":2320},[115,26212,310],{"emptyLinePlaceholder":309},[115,26214,26215,26217,26219],{"class":117,"line":7083},[115,26216,2214],{"class":121},[115,26218,2268],{"class":262},[115,26220,2220],{"class":125},[115,26222,26223,26225],{"class":117,"line":7090},[115,26224,2276],{"class":121},[115,26226,26227],{"class":125},"http:\u002F\u002Fweb:8000;\n",[115,26229,26230,26232],{"class":117,"line":7097},[115,26231,2285],{"class":121},[115,26233,2288],{"class":125},[115,26235,26236,26238],{"class":117,"line":7108},[115,26237,2285],{"class":121},[115,26239,3767],{"class":125},[115,26241,26242,26244],{"class":117,"line":7113},[115,26243,2285],{"class":121},[115,26245,2312],{"class":125},[115,26247,26248,26250],{"class":117,"line":16535},[115,26249,2285],{"class":121},[115,26251,2304],{"class":125},[115,26253,26254,26256,26258],{"class":117,"line":16544},[115,26255,12916],{"class":121},[115,26257,11461],{"class":202},[115,26259,3811],{"class":125},[115,26261,26262],{"class":117,"line":16549},[115,26263,2233],{"class":125},[115,26265,26266],{"class":117,"line":16555},[115,26267,2323],{"class":125},[16,26269,26270],{},"This is the standard reverse proxy pattern for Django with Docker Compose: Nginx terminates TLS, forwards protocol headers, and serves static and media files.",[52,26272,26274],{"id":26273},"tls-note","TLS note",[16,26276,26277,26278,26281],{},"The example above assumes certificate files already exist in ",[20,26279,26280],{},".\u002Fcerts",". That is acceptable, but production also requires a certificate renewal plan. If you do not want to manage certificate files manually, use an ACME-capable proxy workflow such as Caddy or a Certbot-based Nginx setup.",[11,26283,26285],{"id":26284},"_6-handle-migrations-and-static-files-safely","6. Handle migrations and static files safely",[16,26287,26288],{},"Do not run migrations automatically every time the container starts. That makes ordinary restarts risky.",[16,26290,26291],{},"Run migrations explicitly:",[106,26293,26295],{"className":108,"code":26294,"language":110,"meta":111,"style":111},"docker compose run --rm web python manage.py migrate\n",[20,26296,26297],{"__ignoreMap":111},[115,26298,26299,26301,26303,26305,26307,26309,26311,26313],{"class":117,"line":118},[115,26300,3295],{"class":262},[115,26302,3298],{"class":132},[115,26304,18889],{"class":132},[115,26306,18892],{"class":202},[115,26308,3304],{"class":132},[115,26310,19676],{"class":132},[115,26312,1117],{"class":132},[115,26314,11324],{"class":132},[16,26316,26317],{},"Collect static files explicitly too:",[106,26319,26321],{"className":108,"code":26320,"language":110,"meta":111,"style":111},"docker compose run --rm web python manage.py collectstatic --noinput\n",[20,26322,26323],{"__ignoreMap":111},[115,26324,26325,26327,26329,26331,26333,26335,26337,26339,26341],{"class":117,"line":118},[115,26326,3295],{"class":262},[115,26328,3298],{"class":132},[115,26330,18889],{"class":132},[115,26332,18892],{"class":202},[115,26334,3304],{"class":132},[115,26336,19676],{"class":132},[115,26338,1117],{"class":132},[115,26340,1838],{"class":132},[115,26342,1841],{"class":202},[16,26344,26345,26346,26348,26349,26351,26352,26354,26355,26357],{},"If you want Nginx to serve static files from a volume, mount the same named volume at Django’s ",[20,26347,11918],{}," in ",[20,26350,20417],{}," and at the proxy static path in ",[20,26353,2156],{},". Then ",[20,26356,13689],{}," writes into the shared volume that Nginx serves.",[16,26359,6168,26360,26363,26364,26366],{},[20,26361,26362],{},"docker compose run --rm web python manage.py migrate"," fails because PostgreSQL is not ready yet, start ",[20,26365,20420],{}," first and wait briefly before retrying:",[106,26368,26370],{"className":108,"code":26369,"language":110,"meta":111,"style":111},"docker compose up -d db\n",[20,26371,26372],{"__ignoreMap":111},[115,26373,26374,26376,26378,26380,26382],{"class":117,"line":118},[115,26375,3295],{"class":262},[115,26377,3298],{"class":132},[115,26379,3502],{"class":132},[115,26381,1019],{"class":202},[115,26383,26384],{"class":132}," db\n",[16,26386,8572],{},[106,26388,26390],{"className":108,"code":26389,"language":110,"meta":111,"style":111},"docker compose logs web --tail=50\ndocker compose logs nginx --tail=50\n",[20,26391,26392,26405],{"__ignoreMap":111},[115,26393,26394,26396,26398,26400,26402],{"class":117,"line":118},[115,26395,3295],{"class":262},[115,26397,3298],{"class":132},[115,26399,3301],{"class":132},[115,26401,3304],{"class":132},[115,26403,26404],{"class":202}," --tail=50\n",[115,26406,26407,26409,26411,26413,26415],{"class":117,"line":136},[115,26408,3295],{"class":262},[115,26410,3298],{"class":132},[115,26412,3301],{"class":132},[115,26414,3906],{"class":132},[115,26416,26404],{"class":202},[16,26418,26419],{},"Rollback note: take a database backup before destructive or high-risk migrations.",[11,26421,26423],{"id":26422},"_7-deploy-the-stack-on-the-server","7. Deploy the stack on the server",[16,26425,26426],{},"On Ubuntu, install Docker and open only the needed ports:",[106,26428,26430],{"className":108,"code":26429,"language":110,"meta":111,"style":111},"sudo apt-get update\nsudo apt-get install -y docker.io docker-compose-plugin\nsudo systemctl enable --now docker\nsudo ufw allow OpenSSH\nsudo ufw allow 80\u002Ftcp\nsudo ufw allow 443\u002Ftcp\nsudo ufw enable\n",[20,26431,26432,26441,26457,26470,26480,26490,26500],{"__ignoreMap":111},[115,26433,26434,26436,26439],{"class":117,"line":118},[115,26435,2001],{"class":262},[115,26437,26438],{"class":132}," apt-get",[115,26440,6591],{"class":132},[115,26442,26443,26445,26447,26449,26451,26454],{"class":117,"line":136},[115,26444,2001],{"class":262},[115,26446,26438],{"class":132},[115,26448,6600],{"class":132},[115,26450,8432],{"class":202},[115,26452,26453],{"class":132}," docker.io",[115,26455,26456],{"class":132}," docker-compose-plugin\n",[115,26458,26459,26461,26463,26465,26467],{"class":117,"line":149},[115,26460,2001],{"class":262},[115,26462,3480],{"class":132},[115,26464,8567],{"class":132},[115,26466,9433],{"class":202},[115,26468,26469],{"class":132}," docker\n",[115,26471,26472,26474,26476,26478],{"class":117,"line":162},[115,26473,2001],{"class":262},[115,26475,2014],{"class":132},[115,26477,14341],{"class":132},[115,26479,14344],{"class":132},[115,26481,26482,26484,26486,26488],{"class":117,"line":175},[115,26483,2001],{"class":262},[115,26485,2014],{"class":132},[115,26487,14341],{"class":132},[115,26489,24349],{"class":132},[115,26491,26492,26494,26496,26498],{"class":117,"line":350},[115,26493,2001],{"class":262},[115,26495,2014],{"class":132},[115,26497,14341],{"class":132},[115,26499,24360],{"class":132},[115,26501,26502,26504,26506],{"class":117,"line":365},[115,26503,2001],{"class":262},[115,26505,2014],{"class":132},[115,26507,14375],{"class":132},[16,26509,26510],{},"Create the deployment directory:",[106,26512,26514],{"className":108,"code":26513,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fsrv\u002Fmyapp\nsudo chown $USER:$USER \u002Fsrv\u002Fmyapp\ncd \u002Fsrv\u002Fmyapp\n",[20,26515,26516,26526,26540],{"__ignoreMap":111},[115,26517,26518,26520,26522,26524],{"class":117,"line":118},[115,26519,2001],{"class":262},[115,26521,6721],{"class":132},[115,26523,1001],{"class":202},[115,26525,14799],{"class":132},[115,26527,26528,26530,26532,26534,26536,26538],{"class":117,"line":136},[115,26529,2001],{"class":262},[115,26531,6733],{"class":132},[115,26533,12258],{"class":125},[115,26535,241],{"class":132},[115,26537,12263],{"class":125},[115,26539,20533],{"class":132},[115,26541,26542,26544],{"class":117,"line":149},[115,26543,5303],{"class":202},[115,26545,14799],{"class":132},[16,26547,26548],{},"If you build on the server, tag the release image:",[106,26550,26552],{"className":108,"code":26551,"language":110,"meta":111,"style":111},"docker build -t myapp:2026-04-24 .\n",[20,26553,26554],{"__ignoreMap":111},[115,26555,26556,26558,26560,26562,26565],{"class":117,"line":118},[115,26557,3295],{"class":262},[115,26559,17022],{"class":132},[115,26561,3909],{"class":202},[115,26563,26564],{"class":132}," myapp:2026-04-24",[115,26566,17030],{"class":132},[16,26568,26569],{},"Then run the release steps:",[106,26571,26573],{"className":108,"code":26572,"language":110,"meta":111,"style":111},"docker compose up -d db\ndocker compose run --rm web python manage.py migrate\ndocker compose run --rm web python manage.py collectstatic --noinput\ndocker compose up -d --no-build\n",[20,26574,26575,26587,26605,26625],{"__ignoreMap":111},[115,26576,26577,26579,26581,26583,26585],{"class":117,"line":118},[115,26578,3295],{"class":262},[115,26580,3298],{"class":132},[115,26582,3502],{"class":132},[115,26584,1019],{"class":202},[115,26586,26384],{"class":132},[115,26588,26589,26591,26593,26595,26597,26599,26601,26603],{"class":117,"line":136},[115,26590,3295],{"class":262},[115,26592,3298],{"class":132},[115,26594,18889],{"class":132},[115,26596,18892],{"class":202},[115,26598,3304],{"class":132},[115,26600,19676],{"class":132},[115,26602,1117],{"class":132},[115,26604,11324],{"class":132},[115,26606,26607,26609,26611,26613,26615,26617,26619,26621,26623],{"class":117,"line":149},[115,26608,3295],{"class":262},[115,26610,3298],{"class":132},[115,26612,18889],{"class":132},[115,26614,18892],{"class":202},[115,26616,3304],{"class":132},[115,26618,19676],{"class":132},[115,26620,1117],{"class":132},[115,26622,1838],{"class":132},[115,26624,1841],{"class":202},[115,26626,26627,26629,26631,26633,26635],{"class":117,"line":162},[115,26628,3295],{"class":262},[115,26630,3298],{"class":132},[115,26632,3502],{"class":132},[115,26634,1019],{"class":202},[115,26636,26637],{"class":202}," --no-build\n",[16,26639,26640],{},"Check status:",[106,26642,26644],{"className":108,"code":26643,"language":110,"meta":111,"style":111},"docker compose ps\n",[20,26645,26646],{"__ignoreMap":111},[115,26647,26648,26650,26652],{"class":117,"line":118},[115,26649,3295],{"class":262},[115,26651,3298],{"class":132},[115,26653,4790],{"class":132},[16,26655,26656],{},"Check open ports:",[106,26658,26660],{"className":108,"code":26659,"language":110,"meta":111,"style":111},"ss -tulpn | grep -E ':80|:443|:5432|:6379|:8000'\n",[20,26661,26662],{"__ignoreMap":111},[115,26663,26664,26666,26668,26670,26672,26674],{"class":117,"line":118},[115,26665,6472],{"class":262},[115,26667,6475],{"class":202},[115,26669,579],{"class":121},[115,26671,4838],{"class":262},[115,26673,6482],{"class":202},[115,26675,26676],{"class":132}," ':80|:443|:5432|:6379|:8000'\n",[16,26678,26679,26680,3146,26682,211],{},"You should only see public bindings for ",[20,26681,3808],{},[20,26683,2174],{},[11,26685,24509],{"id":19771},[16,26687,24512],{},[106,26689,26691],{"className":108,"code":26690,"language":110,"meta":111,"style":111},"docker compose ps\ndocker compose logs web --tail=100\ndocker compose logs nginx --tail=100\ncurl -I https:\u002F\u002Fexample.com\n",[20,26692,26693,26701,26713,26725],{"__ignoreMap":111},[115,26694,26695,26697,26699],{"class":117,"line":118},[115,26696,3295],{"class":262},[115,26698,3298],{"class":132},[115,26700,4790],{"class":132},[115,26702,26703,26705,26707,26709,26711],{"class":117,"line":136},[115,26704,3295],{"class":262},[115,26706,3298],{"class":132},[115,26708,3301],{"class":132},[115,26710,3304],{"class":132},[115,26712,3307],{"class":202},[115,26714,26715,26717,26719,26721,26723],{"class":117,"line":149},[115,26716,3295],{"class":262},[115,26718,3298],{"class":132},[115,26720,3301],{"class":132},[115,26722,3906],{"class":132},[115,26724,3307],{"class":202},[115,26726,26727,26729,26731],{"class":117,"line":162},[115,26728,2764],{"class":262},[115,26730,2767],{"class":202},[115,26732,2770],{"class":132},[16,26734,26735],{},"Verify:",[63,26737,26738,26744,26746,26749,26752],{},[66,26739,26740,26741,26743],{},"homepage returns ",[20,26742,17741],{}," or the expected redirect",[66,26745,1137],{},[66,26747,26748],{},"static assets load",[66,26750,26751],{},"forms do not fail CSRF checks",[66,26753,26754],{},[20,26755,2707],{},[16,26757,26758],{},"Security checks:",[63,26760,26761,26764,26767,26770,26773],{},[66,26762,26763],{},"PostgreSQL is not publicly reachable",[66,26765,26766],{},"Redis is not publicly reachable",[66,26768,26769],{},"HTTPS redirect works",[66,26771,26772],{},"forwarded headers are present",[66,26774,26775,26776,26778],{},"only ",[20,26777,24692],{}," are exposed externally",[11,26780,26782],{"id":26781},"_9-rollback-and-recovery","9. Rollback and recovery",[16,26784,26785,26786,241],{},"Tag images by release, not only ",[20,26787,19147],{},[106,26789,26791],{"className":108,"code":26790,"language":110,"meta":111,"style":111},"docker build -t myapp:2026-04-24 .\ndocker build -t myapp:2026-04-10 .\n",[20,26792,26793,26805],{"__ignoreMap":111},[115,26794,26795,26797,26799,26801,26803],{"class":117,"line":118},[115,26796,3295],{"class":262},[115,26798,17022],{"class":132},[115,26800,3909],{"class":202},[115,26802,26564],{"class":132},[115,26804,17030],{"class":132},[115,26806,26807,26809,26811,26813,26816],{"class":117,"line":136},[115,26808,3295],{"class":262},[115,26810,17022],{"class":132},[115,26812,3909],{"class":202},[115,26814,26815],{"class":132}," myapp:2026-04-10",[115,26817,17030],{"class":132},[16,26819,26820,26821,26823],{},"In production, point the ",[20,26822,20417],{}," service at a specific image tag such as:",[106,26825,26827],{"className":2485,"code":26826,"language":2487,"meta":111,"style":111},"web:\n  image: myapp:2026-04-24\n",[20,26828,26829,26835],{"__ignoreMap":111},[115,26830,26831,26833],{"class":117,"line":118},[115,26832,20417],{"class":2494},[115,26834,2498],{"class":125},[115,26836,26837,26840,26842],{"class":117,"line":136},[115,26838,26839],{"class":2494},"  image",[115,26841,2513],{"class":125},[115,26843,25750],{"class":132},[16,26845,26846,26847,26849],{},"If a release fails, change the ",[20,26848,20417],{}," image back to the previous tag and recreate the app without rebuilding:",[106,26851,26853],{"className":108,"code":26852,"language":110,"meta":111,"style":111},"docker compose up -d --no-build web nginx\ndocker compose ps\n",[20,26854,26855,26872],{"__ignoreMap":111},[115,26856,26857,26859,26861,26863,26865,26868,26870],{"class":117,"line":118},[115,26858,3295],{"class":262},[115,26860,3298],{"class":132},[115,26862,3502],{"class":132},[115,26864,1019],{"class":202},[115,26866,26867],{"class":202}," --no-build",[115,26869,3304],{"class":132},[115,26871,1996],{"class":132},[115,26873,26874,26876,26878],{"class":117,"line":136},[115,26875,3295],{"class":262},[115,26877,3298],{"class":132},[115,26879,4790],{"class":132},[16,26881,26882],{},"Before risky migrations, create a database backup:",[106,26884,26886],{"className":108,"code":26885,"language":110,"meta":111,"style":111},"docker compose exec -T db sh -c 'pg_dump -U \"$POSTGRES_USER\" \"$POSTGRES_DB\"' > backup.sql\n",[20,26887,26888],{"__ignoreMap":111},[115,26889,26890,26892,26894,26896,26899,26902,26905,26907,26910,26912],{"class":117,"line":118},[115,26891,3295],{"class":262},[115,26893,3298],{"class":132},[115,26895,5258],{"class":132},[115,26897,26898],{"class":202}," -T",[115,26900,26901],{"class":132}," db",[115,26903,26904],{"class":132}," sh",[115,26906,1024],{"class":202},[115,26908,26909],{"class":132}," 'pg_dump -U \"$POSTGRES_USER\" \"$POSTGRES_DB\"'",[115,26911,604],{"class":121},[115,26913,26914],{"class":132}," backup.sql\n",[16,26916,26917],{},"Important: rolling back the application image is only safe when schema changes remain backward compatible. If a migration removes columns, changes constraints, or reshapes data, rollback may require restoring the database, not just switching to an older image.",[11,26919,1321],{"id":1320},[16,26921,26922],{},"This setup works because it separates concerns clearly:",[63,26924,26925,26928,26931,26934,26937,26940],{},[66,26926,26927],{},"Django serves the application",[66,26929,26930],{},"Gunicorn handles Python web requests",[66,26932,26933],{},"Nginx handles public traffic, TLS, and static\u002Fmedia delivery",[66,26935,26936],{},"PostgreSQL stays private on the Docker network",[66,26938,26939],{},"Redis can stay private too, if used",[66,26941,26942],{},"Compose gives you repeatable service definitions on a single host",[16,26944,26945],{},"For production Docker Compose with Django, this is a good fit when you have:",[63,26947,26948,26951,26954,26957],{},[66,26949,26950],{},"one VPS or one Linux server",[66,26952,26953],{},"low to moderate traffic",[66,26955,26956],{},"a small team",[66,26958,26959],{},"no immediate need for rolling deploys or multi-host orchestration",[16,26961,26962],{},"Choose another approach when you need multi-host failover, native rolling deployments, or more advanced scheduling.",[52,26964,26966],{"id":26965},"when-to-turn-this-into-a-script-or-template","When to turn this into a script or template",[16,26968,26969,26970,26972],{},"Once your release steps are stable, the repetitive parts are good candidates for automation: server bootstrap, ",[20,26971,191],{}," validation, image tagging, migrations, health checks, backups, and rollback selection. A reusable template also helps keep proxy headers, volumes, and service definitions consistent across projects.",[11,26974,1337],{"id":1336},[63,26976,26977,26985,26996,27003,27009,27015,27021],{},[66,26978,26979,2957,26982,26984],{},[1226,26980,26981],{},"Database readiness:",[20,26983,9800],{}," is not enough. The database container may be running before PostgreSQL is actually ready.",[66,26986,26987,26990,26991,4493,26993,26995],{},[1226,26988,26989],{},"CSRF and HTTPS issues:"," if ",[20,26992,2377],{},[20,26994,3203],{}," is missing, Django may treat requests as HTTP and reject secure forms.",[66,26997,26998,27000,27001,211],{},[1226,26999,10126],{}," pick one clear strategy. In this guide, Nginx serves static files from a shared volume populated by ",[20,27002,13689],{},[66,27004,27005,27008],{},[1226,27006,27007],{},"Media files:"," named volumes work on a single host. Object storage is usually a better fit if you may move to multiple servers.",[66,27010,27011,27014],{},[1226,27012,27013],{},"TLS lifecycle:"," if you mount certificate files into Nginx, you also need a certificate issuance and renewal process.",[66,27016,27017,27020],{},[1226,27018,27019],{},"Compose limits:"," Docker Compose is useful for one-host deployments, but it does not provide cluster orchestration or native rolling updates.",[66,27022,27023,27026],{},[1226,27024,27025],{},"Gunicorn tuning:"," set worker counts and timeouts based on your app and server size, not copied defaults.",[11,27028,1386],{"id":1385},[16,27030,27031,27032,211],{},"Before deploying with containers, review the broader ",[1395,27033,3000],{"href":2999},[16,27035,27036,27037,211],{},"If you want the non-container equivalent of this architecture, see ",[1395,27038,2986],{"href":2985},[16,27040,27041,27042,211],{},"If you prefer a proxy that handles certificate automation for you, see ",[1395,27043,8046],{"href":8045},[16,27045,27046,27047,211],{},"If you need an ASGI stack for websockets or async views, see ",[1395,27048,8039],{"href":8038},[11,27050,1420],{"id":1419},[52,27052,27054],{"id":27053},"is-docker-compose-suitable-for-django-in-production","Is Docker Compose suitable for Django in production?",[16,27056,27057],{},"Yes, for a single-host deployment. It works well for small teams, client projects, internal tools, and moderate traffic applications. It is less suitable when you need multi-host orchestration or rolling deploys across several servers.",[52,27059,27061],{"id":27060},"should-i-run-migrations-automatically-when-the-container-starts","Should I run migrations automatically when the container starts?",[16,27063,27064],{},"Usually no. Automatic startup migrations turn a simple restart into a schema change event. It is safer to run migrations as an explicit release step so you can back up first and verify the result.",[52,27066,27068],{"id":27067},"do-i-need-nginx-or-caddy-in-front-of-django-when-using-docker-compose","Do I need Nginx or Caddy in front of Django when using Docker Compose?",[16,27070,27071],{},"In most production setups, yes. A reverse proxy handles TLS termination, secure headers, buffering, and static\u002Fmedia delivery more cleanly than exposing Gunicorn directly.",[52,27073,27075],{"id":27074},"how-should-i-handle-static-and-media-files-in-a-docker-compose-deployment","How should I handle static and media files in a Docker Compose deployment?",[16,27077,27078],{},"Static files should usually be collected during the release and served by the reverse proxy. Media files need persistent storage, such as a named volume on a single host or object storage if you may scale beyond one server.",[52,27080,27082],{"id":27081},"what-is-the-safest-rollback-approach-for-a-failed-django-compose-deployment","What is the safest rollback approach for a failed Django Compose deployment?",[16,27084,27085,27086,27088,27089,27092],{},"Keep the previous image tag, switch the ",[20,27087,20417],{}," service back to it, and recreate the affected services with ",[20,27090,27091],{},"--no-build",". For schema-changing releases, back up the database first because application rollback does not undo database changes.",[1485,27094,27095],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":27097},[27098,27099,27100,27101,27104,27105,27106,27107,27111,27112,27113,27114,27115,27118,27119,27120],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":25063,"depth":136,"text":25064,"children":27102},[27103],{"id":25067,"depth":149,"text":25068},{"id":25134,"depth":136,"text":25135},{"id":25496,"depth":136,"text":25497},{"id":25720,"depth":136,"text":25721},{"id":26060,"depth":136,"text":26061,"children":27108},[27109,27110],{"id":26064,"depth":149,"text":26065},{"id":26273,"depth":149,"text":26274},{"id":26284,"depth":136,"text":26285},{"id":26422,"depth":136,"text":26423},{"id":19771,"depth":136,"text":24509},{"id":26781,"depth":136,"text":26782},{"id":1320,"depth":136,"text":1321,"children":27116},[27117],{"id":26965,"depth":149,"text":26966},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":27121},[27122,27123,27124,27125,27126],{"id":27053,"depth":149,"text":27054},{"id":27060,"depth":149,"text":27061},{"id":27067,"depth":149,"text":27068},{"id":27074,"depth":149,"text":27075},{"id":27081,"depth":149,"text":27082},"Many Docker Compose examples for Django are built for local development, not production.",{},"\u002Fdeploy-django-docker-compose-production","7",[2985,8038,14027],{"title":2993,"description":27127},[1557,3295,1558,2156],"deploy-django-docker-compose-production",[1557,3295,1558,2156],"BsvuZaepC4oRGwtyCcAipXjVj63KVdwm8fxu1ousgrU",{"id":27138,"title":27139,"body":27140,"category":3088,"description":28931,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":28932,"navigation":309,"path":28933,"priority":28934,"related":28935,"role":1553,"section":3098,"seo":28936,"stack":28937,"stem":28940,"tags":28941,"type":1561,"__hash__":28942},"articles\u002Fdeploy-django-with-github-actions-cicd.md","Deploy Django with GitHub Actions CI\u002FCD",{"type":8,"value":27141,"toc":28880},[27142,27144,27147,27154,27157,27159,27162,27211,27214,27216,27220,27223,27227,27230,27234,27237,27241,27244,27259,27263,27266,27270,27306,27309,27342,27349,27368,27372,27379,27396,27399,27410,27416,27420,27423,27443,27446,27465,27469,27472,27511,27517,27521,27527,27533,27536,27560,27564,27570,27660,27663,27688,27691,27695,27698,27725,27734,27743,27747,27750,27754,27757,27761,27766,28305,28309,28312,28316,28319,28323,28330,28334,28350,28353,28356,28360,28375,28379,28386,28401,28405,28432,28436,28439,28461,28465,28468,28472,28489,28493,28499,28540,28543,28561,28565,28568,28570,28585,28588,28608,28612,28630,28633,28637,28644,28670,28673,28681,28684,28692,28698,28703,28705,28708,28731,28734,28737,28748,28751,28755,28761,28763,28813,28815,28820,28826,28831,28836,28841,28843,28850,28853,28857,28860,28864,28867,28871,28877],[11,27143,14],{"id":13},[16,27145,27146],{},"Manual Django deployments usually fail in predictable ways: someone forgets to run migrations, static files are outdated, the wrong branch gets pushed to production, or a restart happens before the new release is actually ready. Even when the deploy works, the process is hard to repeat consistently.",[16,27148,27149,27150,27153],{},"A safe ",[1226,27151,27152],{},"Django GitHub Actions deployment"," turns the release into a defined sequence: push approved code, connect securely to the server, create a new release, install dependencies, run migrations, collect static files, switch the active release, restart Gunicorn, verify health, and keep a previous release available for rollback.",[16,27155,27156],{},"GitHub Actions helps with repeatability, but only if the server is already configured correctly and the workflow is limited to controlled production releases.",[11,27158,30],{"id":29},[16,27160,27161],{},"A practical production pattern is:",[1173,27163,27164,27167,27170,27173,27176,27196,27202,27205,27208],{},[66,27165,27166],{},"Trigger a GitHub Actions workflow only from the production branch or a protected environment.",[66,27168,27169],{},"Use SSH with a non-root deploy user.",[66,27171,27172],{},"Upload the application code to a timestamped release directory on the server.",[66,27174,27175],{},"Reuse a server-side virtualenv and shared environment configuration.",[66,27177,27178,27179],{},"Run:\n",[63,27180,27181,27186,27191],{},[66,27182,27183],{},[20,27184,27185],{},"pip install -r requirements.txt",[66,27187,27188],{},[20,27189,27190],{},"python manage.py migrate --noinput",[66,27192,27193],{},[20,27194,27195],{},"python manage.py collectstatic --noinput",[66,27197,27198,27199,27201],{},"Switch ",[20,27200,14814],{}," to the new release.",[66,27203,27204],{},"Restart Gunicorn.",[66,27206,27207],{},"Verify a health endpoint and service status.",[66,27209,27210],{},"Roll back the symlink if checks fail.",[16,27212,27213],{},"This gives you a simple, production-safe GitHub Actions Django deployment pipeline without introducing a full container platform.",[11,27215,43],{"id":42},[11,27217,27219],{"id":27218},"choose-a-safe-deployment-approach-for-django-with-github-actions","Choose a safe deployment approach for Django with GitHub Actions",[16,27221,27222],{},"For most VPS deployments, use one of these patterns.",[52,27224,27226],{"id":27225},"direct-server-deploy-over-ssh","Direct server deploy over SSH",[16,27228,27229],{},"GitHub Actions connects to the server and runs the release steps remotely. This is the simplest option for a single Ubuntu server.",[52,27231,27233],{"id":27232},"build-artifact-first-then-deploy-artifact","Build artifact first, then deploy artifact",[16,27235,27236],{},"Instead of syncing the repository directly, Actions creates a tarball and uploads that to the server. This reduces variation between CI and release input and avoids deploying extra repository files.",[52,27238,27240],{"id":27239},"when-to-avoid-deploying-on-every-push","When to avoid deploying on every push",[16,27242,27243],{},"Do not deploy every branch automatically. Limit production deploys to:",[63,27245,27246,27253,27256],{},[66,27247,27248,4493,27250],{},[20,27249,21584],{},[20,27251,27252],{},"production",[66,27254,27255],{},"tagged releases",[66,27257,27258],{},"a protected GitHub Environment requiring approval",[11,27260,27262],{"id":27261},"prepare-the-production-server-for-cicd-deployments","Prepare the production server for CI\u002FCD deployments",[16,27264,27265],{},"The server should already support a successful manual deployment before automation.",[52,27267,27269],{"id":27268},"create-a-dedicated-deploy-user","Create a dedicated deploy user",[106,27271,27273],{"className":108,"code":27272,"language":110,"meta":111,"style":111},"sudo adduser deploy\nsudo mkdir -p \u002Fsrv\u002Fmyapp\u002F{releases,shared,run}\nsudo chown -R deploy:deploy \u002Fsrv\u002Fmyapp\n",[20,27274,27275,27283,27294],{"__ignoreMap":111},[115,27276,27277,27279,27281],{"class":117,"line":118},[115,27278,2001],{"class":262},[115,27280,14212],{"class":132},[115,27282,14215],{"class":132},[115,27284,27285,27287,27289,27291],{"class":117,"line":136},[115,27286,2001],{"class":262},[115,27288,6721],{"class":132},[115,27290,1001],{"class":202},[115,27292,27293],{"class":132}," \u002Fsrv\u002Fmyapp\u002F{releases,shared,run}\n",[115,27295,27296,27298,27300,27302,27304],{"class":117,"line":149},[115,27297,2001],{"class":262},[115,27299,6733],{"class":132},[115,27301,6736],{"class":202},[115,27303,14264],{"class":132},[115,27305,14799],{"class":132},[16,27307,27308],{},"Set up the deploy user's SSH directory:",[106,27310,27312],{"className":108,"code":27311,"language":110,"meta":111,"style":111},"sudo -u deploy mkdir -p \u002Fhome\u002Fdeploy\u002F.ssh\nsudo -u deploy chmod 700 \u002Fhome\u002Fdeploy\u002F.ssh\n",[20,27313,27314,27328],{"__ignoreMap":111},[115,27315,27316,27318,27320,27322,27324,27326],{"class":117,"line":118},[115,27317,2001],{"class":262},[115,27319,2788],{"class":202},[115,27321,19270],{"class":132},[115,27323,6721],{"class":132},[115,27325,1001],{"class":202},[115,27327,14241],{"class":132},[115,27329,27330,27332,27334,27336,27338,27340],{"class":117,"line":136},[115,27331,2001],{"class":262},[115,27333,2788],{"class":202},[115,27335,19270],{"class":132},[115,27337,12480],{"class":132},[115,27339,14275],{"class":202},[115,27341,14241],{"class":132},[16,27343,27344,27345,27348],{},"Add the CI public key to ",[20,27346,27347],{},"\u002Fhome\u002Fdeploy\u002F.ssh\u002Fauthorized_keys"," and set:",[106,27350,27352],{"className":108,"code":27351,"language":110,"meta":111,"style":111},"sudo -u deploy chmod 600 \u002Fhome\u002Fdeploy\u002F.ssh\u002Fauthorized_keys\n",[20,27353,27354],{"__ignoreMap":111},[115,27355,27356,27358,27360,27362,27364,27366],{"class":117,"line":118},[115,27357,2001],{"class":262},[115,27359,2788],{"class":202},[115,27361,19270],{"class":132},[115,27363,12480],{"class":132},[115,27365,266],{"class":202},[115,27367,14288],{"class":132},[52,27369,27371],{"id":27370},"allow-the-deploy-user-to-restart-gunicorn-without-a-password","Allow the deploy user to restart Gunicorn without a password",[16,27373,27374,27375,27378],{},"If your workflow runs ",[20,27376,27377],{},"sudo systemctl restart gunicorn",", configure that explicitly. Otherwise CI will block on a password prompt.",[106,27380,27382],{"className":108,"code":27381,"language":110,"meta":111,"style":111},"sudo visudo -f \u002Fetc\u002Fsudoers.d\u002Fmyapp-deploy\n",[20,27383,27384],{"__ignoreMap":111},[115,27385,27386,27388,27391,27393],{"class":117,"line":118},[115,27387,2001],{"class":262},[115,27389,27390],{"class":132}," visudo",[115,27392,2777],{"class":202},[115,27394,27395],{"class":132}," \u002Fetc\u002Fsudoers.d\u002Fmyapp-deploy\n",[16,27397,27398],{},"Add:",[106,27400,27404],{"className":27401,"code":27402,"language":27403,"meta":111,"style":111},"language-sudoers shiki shiki-themes github-light github-dark","deploy ALL=NOPASSWD: \u002Fbin\u002Fsystemctl restart gunicorn, \u002Fbin\u002Fsystemctl status gunicorn\n","sudoers",[20,27405,27406],{"__ignoreMap":111},[115,27407,27408],{"class":117,"line":118},[115,27409,27402],{},[16,27411,27412,27413,27415],{},"If your distro uses a different ",[20,27414,1981],{}," path, adjust it accordingly.",[52,27417,27419],{"id":27418},"set-up-application-directories-and-release-layout","Set up application directories and release layout",[16,27421,27422],{},"Use a release structure like this:",[63,27424,27425,27429,27433,27438],{},[66,27426,27427],{},[20,27428,14809],{},[66,27430,27431,14815],{},[20,27432,14814],{},[66,27434,27435],{},[20,27436,27437],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002F.env",[66,27439,27440],{},[20,27441,27442],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\u002F",[16,27444,27445],{},"Create shared paths:",[106,27447,27449],{"className":108,"code":27448,"language":110,"meta":111,"style":111},"sudo -u deploy mkdir -p \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\n",[20,27450,27451],{"__ignoreMap":111},[115,27452,27453,27455,27457,27459,27461,27463],{"class":117,"line":118},[115,27454,2001],{"class":262},[115,27456,2788],{"class":202},[115,27458,19270],{"class":132},[115,27460,6721],{"class":132},[115,27462,1001],{"class":202},[115,27464,14786],{"class":132},[52,27466,27468],{"id":27467},"configure-python-environment-and-dependency-installation","Configure Python environment and dependency installation",[16,27470,27471],{},"Create one virtualenv outside the release directories:",[106,27473,27475],{"className":108,"code":27474,"language":110,"meta":111,"style":111},"sudo -u deploy python3 -m venv \u002Fsrv\u002Fmyapp\u002Fvenv\nsudo -u deploy \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip install --upgrade pip\n",[20,27476,27477,27494],{"__ignoreMap":111},[115,27478,27479,27481,27483,27485,27487,27489,27491],{"class":117,"line":118},[115,27480,2001],{"class":262},[115,27482,2788],{"class":202},[115,27484,19270],{"class":132},[115,27486,14474],{"class":132},[115,27488,12284],{"class":202},[115,27490,12287],{"class":132},[115,27492,27493],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fvenv\n",[115,27495,27496,27498,27500,27502,27505,27507,27509],{"class":117,"line":136},[115,27497,2001],{"class":262},[115,27499,2788],{"class":202},[115,27501,19270],{"class":132},[115,27503,27504],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip",[115,27506,6600],{"class":132},[115,27508,12338],{"class":202},[115,27510,12341],{"class":132},[16,27512,27513,27514,27516],{},"A shared virtualenv is common on a single VPS, but it weakens rollback isolation. If one deploy upgrades dependencies, switching ",[20,27515,13654],{}," back to older code may not fully restore service. For stronger rollback behavior, use per-release virtualenvs or deploy a prebuilt runtime artifact.",[52,27518,27520],{"id":27519},"keep-runtime-secrets-on-the-server","Keep runtime secrets on the server",[16,27522,27523,27524,27526],{},"Store production secrets in ",[20,27525,27437],{},", not in the repository and not inside the uploaded release archive.",[16,27528,27529,27530,27532],{},"At minimum, production should already have a valid ",[20,27531,2713],{}," outside the repo.",[16,27534,27535],{},"Before automating deploys, confirm production settings are already correct:",[63,27537,27538,27542,27547,27552,27557],{},[66,27539,27540],{},[20,27541,2707],{},[66,27543,27544,27546],{},[20,27545,2719],{}," includes the real hostnames",[66,27548,27549,27551],{},[20,27550,2725],{}," matches your HTTPS origins",[66,27553,27554,27556],{},[20,27555,2377],{}," is configured correctly behind Nginx if TLS terminates at the proxy",[66,27558,27559],{},"secure cookie settings are enabled where appropriate",[52,27561,27563],{"id":27562},"ensure-gunicorn-nginx-and-systemd-are-already-working-manually","Ensure Gunicorn, Nginx, and systemd are already working manually",[16,27565,27566,27567,27569],{},"Your app service should already run from ",[20,27568,14814],{},". Example systemd service:",[106,27571,27573],{"className":2026,"code":27572,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for myapp\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fsrv\u002Fmyapp\u002Fshared\u002F.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn myproject.wsgi:application \\\n    --bind 127.0.0.1:8000 \\\n    --workers 3\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n",[20,27574,27575,27579,27585,27591,27595,27599,27605,27611,27617,27624,27631,27635,27640,27646,27650,27654],{"__ignoreMap":111},[115,27576,27577],{"class":117,"line":118},[115,27578,2035],{"class":262},[115,27580,27581,27583],{"class":117,"line":136},[115,27582,2040],{"class":121},[115,27584,15353],{"class":125},[115,27586,27587,27589],{"class":117,"line":149},[115,27588,2048],{"class":121},[115,27590,2051],{"class":125},[115,27592,27593],{"class":117,"line":162},[115,27594,310],{"emptyLinePlaceholder":309},[115,27596,27597],{"class":117,"line":175},[115,27598,2060],{"class":262},[115,27600,27601,27603],{"class":117,"line":350},[115,27602,2065],{"class":121},[115,27604,12548],{"class":125},[115,27606,27607,27609],{"class":117,"line":365},[115,27608,2073],{"class":121},[115,27610,2076],{"class":125},[115,27612,27613,27615],{"class":117,"line":380},[115,27614,2081],{"class":121},[115,27616,4905],{"class":125},[115,27618,27619,27621],{"class":117,"line":487},[115,27620,2089],{"class":121},[115,27622,27623],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fshared\u002F.env\n",[115,27625,27626,27628],{"class":117,"line":2095},[115,27627,2107],{"class":121},[115,27629,27630],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn myproject.wsgi:application \\\n",[115,27632,27633],{"class":117,"line":2104},[115,27634,23722],{"class":125},[115,27636,27637],{"class":117,"line":2113},[115,27638,27639],{"class":125},"    --workers 3\n",[115,27641,27642,27644],{"class":117,"line":2122},[115,27643,2116],{"class":121},[115,27645,4932],{"class":125},[115,27647,27648],{"class":117,"line":2131},[115,27649,310],{"emptyLinePlaceholder":309},[115,27651,27652],{"class":117,"line":2136},[115,27653,2139],{"class":262},[115,27655,27656,27658],{"class":117,"line":2142},[115,27657,2145],{"class":121},[115,27659,2148],{"class":125},[16,27661,27662],{},"Verify before adding CI\u002FCD:",[106,27664,27666],{"className":108,"code":27665,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn --no-pager\nsudo nginx -t\n",[20,27667,27668,27680],{"__ignoreMap":111},[115,27669,27670,27672,27674,27676,27678],{"class":117,"line":118},[115,27671,2001],{"class":262},[115,27673,3480],{"class":132},[115,27675,1984],{"class":132},[115,27677,2791],{"class":132},[115,27679,2800],{"class":202},[115,27681,27682,27684,27686],{"class":117,"line":136},[115,27683,2001],{"class":262},[115,27685,3906],{"class":132},[115,27687,4282],{"class":202},[16,27689,27690],{},"Also make sure Nginx serves static and media from stable paths, not from temporary release-relative paths that disappear on rollback.",[11,27692,27694],{"id":27693},"configure-github-actions-secrets-securely","Configure GitHub Actions secrets securely",[16,27696,27697],{},"Store these in GitHub repository secrets or, preferably, a protected Environment:",[63,27699,27700,27705,27710,27715,27720],{},[66,27701,27702],{},[20,27703,27704],{},"DEPLOY_HOST",[66,27706,27707],{},[20,27708,27709],{},"DEPLOY_PORT",[66,27711,27712],{},[20,27713,27714],{},"DEPLOY_USER",[66,27716,27717],{},[20,27718,27719],{},"DEPLOY_SSH_KEY",[66,27721,27722],{},[20,27723,27724],{},"DEPLOY_KNOWN_HOSTS",[16,27726,27727,27729,27730,27733],{},[20,27728,27724],{}," should contain the pinned SSH host key entry for the server. Do not treat ",[20,27731,27732],{},"ssh-keyscan"," output collected during the workflow as trusted verification by itself.",[16,27735,27736,27737,27739,27740,27742],{},"Keep runtime app secrets on the server in ",[20,27738,27437],{},". Do not copy production ",[20,27741,191],{}," files through CI artifacts.",[52,27744,27746],{"id":27745},"restrict-deployments-to-protected-branches-or-environments","Restrict deployments to protected branches or environments",[16,27748,27749],{},"GitHub Environments let you require approvals and scope secrets to production. That is safer than exposing deploy credentials to every branch workflow.",[52,27751,27753],{"id":27752},"avoiding-secret-leakage-in-logs","Avoiding secret leakage in logs",[16,27755,27756],{},"Do not echo secrets. Avoid commands that print private keys or environment files. Keep shell scripts strict and minimal.",[11,27758,27760],{"id":27759},"create-the-github-actions-workflow-for-django-deployment","Create the GitHub Actions workflow for Django deployment",[16,27762,8628,27763,241],{},[20,27764,27765],{},".github\u002Fworkflows\u002Fdeploy.yml",[106,27767,27769],{"className":2485,"code":27768,"language":2487,"meta":111,"style":111},"name: Deploy Django\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: production\n\n    steps:\n      - name: Check out code\n        uses: actions\u002Fcheckout@v4\n\n      - name: Set up Python\n        uses: actions\u002Fsetup-python@v5\n        with:\n          python-version: \"3.12\"\n\n      - name: Install dependencies for checks\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements.txt\n\n      - name: Run Django system checks\n        run: |\n          python manage.py check --deploy\n        env:\n          DJANGO_SETTINGS_MODULE: myproject.settings\n          # Add any minimum required environment values here if your\n          # settings module cannot load without them. Keep secrets in\n          # GitHub Environment secrets or on the server, not in the repo.\n\n      - name: Start SSH agent\n        uses: webfactory\u002Fssh-agent@v0.9.0\n        with:\n          ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}\n\n      - name: Install pinned known_hosts\n        run: |\n          mkdir -p ~\u002F.ssh\n          chmod 700 ~\u002F.ssh\n          printf \"%s\\n\" \"${{ secrets.DEPLOY_KNOWN_HOSTS }}\" > ~\u002F.ssh\u002Fknown_hosts\n          chmod 600 ~\u002F.ssh\u002Fknown_hosts\n\n      - name: Upload release\n        run: |\n          RELEASE_ID=$(date +%Y%m%d-%H%M%S)\n          echo \"RELEASE_ID=$RELEASE_ID\" >> $GITHUB_ENV\n          tar --exclude='.git' -czf release.tar.gz .\n\n          ssh -p \"${{ secrets.DEPLOY_PORT }}\" \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \\\n            \"mkdir -p \u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\"\n\n          scp -P \"${{ secrets.DEPLOY_PORT }}\" release.tar.gz \\\n            \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:\u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\u002F\"\n\n      - name: Run remote deploy\n        run: |\n          ssh -p \"${{ secrets.DEPLOY_PORT }}\" \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \u003C\u003CEOF\n          set -e\n          RELEASE_PATH=\u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\n          cd \\$RELEASE_PATH\n          tar -xzf release.tar.gz\n          rm release.tar.gz\n\n          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip install -r requirements.txt\n          ln -sfn \u002Fsrv\u002Fmyapp\u002Fshared\u002F.env .env\n          ln -sfn \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia media\n\n          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py migrate --noinput\n          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py collectstatic --noinput\n\n          ln -sfn \\$RELEASE_PATH \u002Fsrv\u002Fmyapp\u002Fcurrent\n          sudo systemctl restart gunicorn\n          sudo systemctl status gunicorn --no-pager\n          curl --fail --silent http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F > \u002Fdev\u002Fnull\n          EOF\n",[20,27770,27771,27780,27784,27791,27798,27805,27812,27816,27823,27830,27840,27850,27854,27861,27872,27882,27886,27897,27906,27913,27923,27927,27938,27948,27953,27958,27962,27973,27981,27986,27992,28002,28007,28012,28017,28021,28032,28041,28047,28057,28061,28072,28080,28085,28090,28095,28100,28104,28115,28123,28128,28133,28138,28142,28147,28152,28156,28162,28168,28173,28185,28194,28200,28206,28212,28218,28224,28230,28235,28241,28247,28253,28258,28264,28270,28275,28281,28287,28293,28299],{"__ignoreMap":111},[115,27772,27773,27775,27777],{"class":117,"line":118},[115,27774,20820],{"class":2494},[115,27776,2513],{"class":125},[115,27778,27779],{"class":132},"Deploy Django\n",[115,27781,27782],{"class":117,"line":136},[115,27783,310],{"emptyLinePlaceholder":309},[115,27785,27786,27789],{"class":117,"line":149},[115,27787,27788],{"class":202},"on",[115,27790,2498],{"class":125},[115,27792,27793,27796],{"class":117,"line":162},[115,27794,27795],{"class":2494},"  push",[115,27797,2498],{"class":125},[115,27799,27800,27803],{"class":117,"line":175},[115,27801,27802],{"class":2494},"    branches",[115,27804,2498],{"class":125},[115,27806,27807,27809],{"class":117,"line":350},[115,27808,5976],{"class":125},[115,27810,27811],{"class":132},"main\n",[115,27813,27814],{"class":117,"line":365},[115,27815,310],{"emptyLinePlaceholder":309},[115,27817,27818,27821],{"class":117,"line":380},[115,27819,27820],{"class":2494},"jobs",[115,27822,2498],{"class":125},[115,27824,27825,27828],{"class":117,"line":487},[115,27826,27827],{"class":2494},"  deploy",[115,27829,2498],{"class":125},[115,27831,27832,27835,27837],{"class":117,"line":2095},[115,27833,27834],{"class":2494},"    runs-on",[115,27836,2513],{"class":125},[115,27838,27839],{"class":132},"ubuntu-latest\n",[115,27841,27842,27845,27847],{"class":117,"line":2104},[115,27843,27844],{"class":2494},"    environment",[115,27846,2513],{"class":125},[115,27848,27849],{"class":132},"production\n",[115,27851,27852],{"class":117,"line":2113},[115,27853,310],{"emptyLinePlaceholder":309},[115,27855,27856,27859],{"class":117,"line":2122},[115,27857,27858],{"class":2494},"    steps",[115,27860,2498],{"class":125},[115,27862,27863,27865,27867,27869],{"class":117,"line":2131},[115,27864,5976],{"class":125},[115,27866,20820],{"class":2494},[115,27868,2513],{"class":125},[115,27870,27871],{"class":132},"Check out code\n",[115,27873,27874,27877,27879],{"class":117,"line":2136},[115,27875,27876],{"class":2494},"        uses",[115,27878,2513],{"class":125},[115,27880,27881],{"class":132},"actions\u002Fcheckout@v4\n",[115,27883,27884],{"class":117,"line":2142},[115,27885,310],{"emptyLinePlaceholder":309},[115,27887,27888,27890,27892,27894],{"class":117,"line":2273},[115,27889,5976],{"class":125},[115,27891,20820],{"class":2494},[115,27893,2513],{"class":125},[115,27895,27896],{"class":132},"Set up Python\n",[115,27898,27899,27901,27903],{"class":117,"line":2282},[115,27900,27876],{"class":2494},[115,27902,2513],{"class":125},[115,27904,27905],{"class":132},"actions\u002Fsetup-python@v5\n",[115,27907,27908,27911],{"class":117,"line":2291},[115,27909,27910],{"class":2494},"        with",[115,27912,2498],{"class":125},[115,27914,27915,27918,27920],{"class":117,"line":2299},[115,27916,27917],{"class":2494},"          python-version",[115,27919,2513],{"class":125},[115,27921,27922],{"class":132},"\"3.12\"\n",[115,27924,27925],{"class":117,"line":2307},[115,27926,310],{"emptyLinePlaceholder":309},[115,27928,27929,27931,27933,27935],{"class":117,"line":2315},[115,27930,5976],{"class":125},[115,27932,20820],{"class":2494},[115,27934,2513],{"class":125},[115,27936,27937],{"class":132},"Install dependencies for checks\n",[115,27939,27940,27943,27945],{"class":117,"line":2320},[115,27941,27942],{"class":2494},"        run",[115,27944,2513],{"class":125},[115,27946,27947],{"class":121},"|\n",[115,27949,27950],{"class":117,"line":7083},[115,27951,27952],{"class":132},"          python -m pip install --upgrade pip\n",[115,27954,27955],{"class":117,"line":7090},[115,27956,27957],{"class":132},"          pip install -r requirements.txt\n",[115,27959,27960],{"class":117,"line":7097},[115,27961,310],{"emptyLinePlaceholder":309},[115,27963,27964,27966,27968,27970],{"class":117,"line":7108},[115,27965,5976],{"class":125},[115,27967,20820],{"class":2494},[115,27969,2513],{"class":125},[115,27971,27972],{"class":132},"Run Django system checks\n",[115,27974,27975,27977,27979],{"class":117,"line":7113},[115,27976,27942],{"class":2494},[115,27978,2513],{"class":125},[115,27980,27947],{"class":121},[115,27982,27983],{"class":117,"line":16535},[115,27984,27985],{"class":132},"          python manage.py check --deploy\n",[115,27987,27988,27990],{"class":117,"line":16544},[115,27989,16722],{"class":2494},[115,27991,2498],{"class":125},[115,27993,27994,27997,27999],{"class":117,"line":16549},[115,27995,27996],{"class":2494},"          DJANGO_SETTINGS_MODULE",[115,27998,2513],{"class":125},[115,28000,28001],{"class":132},"myproject.settings\n",[115,28003,28004],{"class":117,"line":16555},[115,28005,28006],{"class":3861},"          # Add any minimum required environment values here if your\n",[115,28008,28009],{"class":117,"line":16564},[115,28010,28011],{"class":3861},"          # settings module cannot load without them. Keep secrets in\n",[115,28013,28014],{"class":117,"line":16573},[115,28015,28016],{"class":3861},"          # GitHub Environment secrets or on the server, not in the repo.\n",[115,28018,28019],{"class":117,"line":16582},[115,28020,310],{"emptyLinePlaceholder":309},[115,28022,28023,28025,28027,28029],{"class":117,"line":16587},[115,28024,5976],{"class":125},[115,28026,20820],{"class":2494},[115,28028,2513],{"class":125},[115,28030,28031],{"class":132},"Start SSH agent\n",[115,28033,28034,28036,28038],{"class":117,"line":16596},[115,28035,27876],{"class":2494},[115,28037,2513],{"class":125},[115,28039,28040],{"class":132},"webfactory\u002Fssh-agent@v0.9.0\n",[115,28042,28043,28045],{"class":117,"line":16609},[115,28044,27910],{"class":2494},[115,28046,2498],{"class":125},[115,28048,28049,28052,28054],{"class":117,"line":16614},[115,28050,28051],{"class":2494},"          ssh-private-key",[115,28053,2513],{"class":125},[115,28055,28056],{"class":132},"${{ secrets.DEPLOY_SSH_KEY }}\n",[115,28058,28059],{"class":117,"line":16624},[115,28060,310],{"emptyLinePlaceholder":309},[115,28062,28063,28065,28067,28069],{"class":117,"line":16632},[115,28064,5976],{"class":125},[115,28066,20820],{"class":2494},[115,28068,2513],{"class":125},[115,28070,28071],{"class":132},"Install pinned known_hosts\n",[115,28073,28074,28076,28078],{"class":117,"line":16640},[115,28075,27942],{"class":2494},[115,28077,2513],{"class":125},[115,28079,27947],{"class":121},[115,28081,28082],{"class":117,"line":16646},[115,28083,28084],{"class":132},"          mkdir -p ~\u002F.ssh\n",[115,28086,28087],{"class":117,"line":16651},[115,28088,28089],{"class":132},"          chmod 700 ~\u002F.ssh\n",[115,28091,28092],{"class":117,"line":16656},[115,28093,28094],{"class":132},"          printf \"%s\\n\" \"${{ secrets.DEPLOY_KNOWN_HOSTS }}\" > ~\u002F.ssh\u002Fknown_hosts\n",[115,28096,28097],{"class":117,"line":16666},[115,28098,28099],{"class":132},"          chmod 600 ~\u002F.ssh\u002Fknown_hosts\n",[115,28101,28102],{"class":117,"line":16674},[115,28103,310],{"emptyLinePlaceholder":309},[115,28105,28106,28108,28110,28112],{"class":117,"line":16687},[115,28107,5976],{"class":125},[115,28109,20820],{"class":2494},[115,28111,2513],{"class":125},[115,28113,28114],{"class":132},"Upload release\n",[115,28116,28117,28119,28121],{"class":117,"line":16692},[115,28118,27942],{"class":2494},[115,28120,2513],{"class":125},[115,28122,27947],{"class":121},[115,28124,28125],{"class":117,"line":16697},[115,28126,28127],{"class":132},"          RELEASE_ID=$(date +%Y%m%d-%H%M%S)\n",[115,28129,28130],{"class":117,"line":16702},[115,28131,28132],{"class":132},"          echo \"RELEASE_ID=$RELEASE_ID\" >> $GITHUB_ENV\n",[115,28134,28135],{"class":117,"line":16711},[115,28136,28137],{"class":132},"          tar --exclude='.git' -czf release.tar.gz .\n",[115,28139,28140],{"class":117,"line":16719},[115,28141,310],{"emptyLinePlaceholder":309},[115,28143,28144],{"class":117,"line":16732},[115,28145,28146],{"class":132},"          ssh -p \"${{ secrets.DEPLOY_PORT }}\" \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \\\n",[115,28148,28149],{"class":117,"line":16745},[115,28150,28151],{"class":132},"            \"mkdir -p \u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\"\n",[115,28153,28154],{"class":117,"line":16751},[115,28155,310],{"emptyLinePlaceholder":309},[115,28157,28159],{"class":117,"line":28158},57,[115,28160,28161],{"class":132},"          scp -P \"${{ secrets.DEPLOY_PORT }}\" release.tar.gz \\\n",[115,28163,28165],{"class":117,"line":28164},58,[115,28166,28167],{"class":132},"            \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:\u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\u002F\"\n",[115,28169,28171],{"class":117,"line":28170},59,[115,28172,310],{"emptyLinePlaceholder":309},[115,28174,28176,28178,28180,28182],{"class":117,"line":28175},60,[115,28177,5976],{"class":125},[115,28179,20820],{"class":2494},[115,28181,2513],{"class":125},[115,28183,28184],{"class":132},"Run remote deploy\n",[115,28186,28188,28190,28192],{"class":117,"line":28187},61,[115,28189,27942],{"class":2494},[115,28191,2513],{"class":125},[115,28193,27947],{"class":121},[115,28195,28197],{"class":117,"line":28196},62,[115,28198,28199],{"class":132},"          ssh -p \"${{ secrets.DEPLOY_PORT }}\" \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \u003C\u003CEOF\n",[115,28201,28203],{"class":117,"line":28202},63,[115,28204,28205],{"class":132},"          set -e\n",[115,28207,28209],{"class":117,"line":28208},64,[115,28210,28211],{"class":132},"          RELEASE_PATH=\u002Fsrv\u002Fmyapp\u002Freleases\u002F$RELEASE_ID\n",[115,28213,28215],{"class":117,"line":28214},65,[115,28216,28217],{"class":132},"          cd \\$RELEASE_PATH\n",[115,28219,28221],{"class":117,"line":28220},66,[115,28222,28223],{"class":132},"          tar -xzf release.tar.gz\n",[115,28225,28227],{"class":117,"line":28226},67,[115,28228,28229],{"class":132},"          rm release.tar.gz\n",[115,28231,28233],{"class":117,"line":28232},68,[115,28234,310],{"emptyLinePlaceholder":309},[115,28236,28238],{"class":117,"line":28237},69,[115,28239,28240],{"class":132},"          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip install -r requirements.txt\n",[115,28242,28244],{"class":117,"line":28243},70,[115,28245,28246],{"class":132},"          ln -sfn \u002Fsrv\u002Fmyapp\u002Fshared\u002F.env .env\n",[115,28248,28250],{"class":117,"line":28249},71,[115,28251,28252],{"class":132},"          ln -sfn \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia media\n",[115,28254,28256],{"class":117,"line":28255},72,[115,28257,310],{"emptyLinePlaceholder":309},[115,28259,28261],{"class":117,"line":28260},73,[115,28262,28263],{"class":132},"          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py migrate --noinput\n",[115,28265,28267],{"class":117,"line":28266},74,[115,28268,28269],{"class":132},"          \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py collectstatic --noinput\n",[115,28271,28273],{"class":117,"line":28272},75,[115,28274,310],{"emptyLinePlaceholder":309},[115,28276,28278],{"class":117,"line":28277},76,[115,28279,28280],{"class":132},"          ln -sfn \\$RELEASE_PATH \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[115,28282,28284],{"class":117,"line":28283},77,[115,28285,28286],{"class":132},"          sudo systemctl restart gunicorn\n",[115,28288,28290],{"class":117,"line":28289},78,[115,28291,28292],{"class":132},"          sudo systemctl status gunicorn --no-pager\n",[115,28294,28296],{"class":117,"line":28295},79,[115,28297,28298],{"class":132},"          curl --fail --silent http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F > \u002Fdev\u002Fnull\n",[115,28300,28302],{"class":117,"line":28301},80,[115,28303,28304],{"class":132},"          EOF\n",[11,28306,28308],{"id":28307},"run-the-django-release-steps-in-the-correct-order","Run the Django release steps in the correct order",[16,28310,28311],{},"The order matters.",[52,28313,28315],{"id":28314},"unpack-the-new-release-into-a-fresh-directory","Unpack the new release into a fresh directory",[16,28317,28318],{},"Use a new timestamped directory for each deploy. Do not deploy in place over the current release.",[52,28320,28322],{"id":28321},"install-pinned-dependencies","Install pinned dependencies",[16,28324,28325,28326,28329],{},"Use pinned versions in ",[20,28327,28328],{},"requirements.txt",". Installing into a shared virtualenv is common on a single VPS, but dependency changes should be intentional and tested.",[52,28331,28333],{"id":28332},"run-database-migrations-safely","Run database migrations safely",[106,28335,28337],{"className":108,"code":28336,"language":110,"meta":111,"style":111},"\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py migrate --noinput\n",[20,28338,28339],{"__ignoreMap":111},[115,28340,28341,28344,28346,28348],{"class":117,"line":118},[115,28342,28343],{"class":262},"\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython",[115,28345,1117],{"class":132},[115,28347,1826],{"class":132},[115,28349,1841],{"class":202},[16,28351,28352],{},"Before risky schema changes, make sure a database backup exists. If a migration is not backward compatible, rollback may require restoring the database rather than only switching code.",[16,28354,28355],{},"Also note the transition risk: if the old code remains live while migrations have already changed the schema, old application code may stop working before the restart. Plan backward-compatible migrations when possible.",[52,28357,28359],{"id":28358},"collect-static-files","Collect static files",[106,28361,28363],{"className":108,"code":28362,"language":110,"meta":111,"style":111},"\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py collectstatic --noinput\n",[20,28364,28365],{"__ignoreMap":111},[115,28366,28367,28369,28371,28373],{"class":117,"line":118},[115,28368,28343],{"class":262},[115,28370,1117],{"class":132},[115,28372,1838],{"class":132},[115,28374,1841],{"class":202},[52,28376,28378],{"id":28377},"switch-the-active-release-symlink","Switch the active release symlink",[16,28380,28381,28382,28385],{},"Because the systemd service uses ",[20,28383,28384],{},"WorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent",", switch the symlink before restarting Gunicorn:",[106,28387,28389],{"className":108,"code":28388,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000 \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[20,28390,28391],{"__ignoreMap":111},[115,28392,28393,28395,28397,28399],{"class":117,"line":118},[115,28394,14854],{"class":262},[115,28396,14857],{"class":202},[115,28398,14860],{"class":132},[115,28400,5306],{"class":132},[52,28402,28404],{"id":28403},"restart-gunicorn","Restart Gunicorn",[106,28406,28408],{"className":108,"code":28407,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn\nsudo systemctl status gunicorn --no-pager\n",[20,28409,28410,28420],{"__ignoreMap":111},[115,28411,28412,28414,28416,28418],{"class":117,"line":118},[115,28413,2001],{"class":262},[115,28415,3480],{"class":132},[115,28417,3483],{"class":132},[115,28419,1987],{"class":132},[115,28421,28422,28424,28426,28428,28430],{"class":117,"line":136},[115,28423,2001],{"class":262},[115,28425,3480],{"class":132},[115,28427,1984],{"class":132},[115,28429,2791],{"class":132},[115,28431,2800],{"class":202},[52,28433,28435],{"id":28434},"reload-nginx-only-if-config-changed","Reload Nginx only if config changed",[16,28437,28438],{},"Usually an app deploy does not require an Nginx reload. If you changed site config:",[106,28440,28441],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,28442,28443,28451],{"__ignoreMap":111},[115,28444,28445,28447,28449],{"class":117,"line":118},[115,28446,2001],{"class":262},[115,28448,3906],{"class":132},[115,28450,4282],{"class":202},[115,28452,28453,28455,28457,28459],{"class":117,"line":136},[115,28454,2001],{"class":262},[115,28456,3480],{"class":132},[115,28458,3919],{"class":132},[115,28460,1996],{"class":132},[11,28462,28464],{"id":28463},"add-verification-checks-after-deployment","Add verification checks after deployment",[16,28466,28467],{},"Run checks immediately after restart.",[52,28469,28471],{"id":28470},"check-systemd-service-status","Check systemd service status",[106,28473,28475],{"className":108,"code":28474,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn --no-pager\n",[20,28476,28477],{"__ignoreMap":111},[115,28478,28479,28481,28483,28485,28487],{"class":117,"line":118},[115,28480,2001],{"class":262},[115,28482,3480],{"class":132},[115,28484,1984],{"class":132},[115,28486,2791],{"class":132},[115,28488,2800],{"class":202},[52,28490,28492],{"id":28491},"verify-the-application-health-endpoint","Verify the application health endpoint",[16,28494,28495,28496,28498],{},"Add a minimal Django endpoint such as ",[20,28497,13411],{}," returning HTTP 200:",[106,28500,28502],{"className":2369,"code":28501,"language":1114,"meta":111,"style":111},"from django.http import JsonResponse\n\ndef health(request):\n    return JsonResponse({\"status\": \"ok\"})\n",[20,28503,28504,28514,28518,28526],{"__ignoreMap":111},[115,28505,28506,28508,28510,28512],{"class":117,"line":118},[115,28507,5621],{"class":121},[115,28509,17240],{"class":125},[115,28511,5613],{"class":121},[115,28513,18598],{"class":125},[115,28515,28516],{"class":117,"line":136},[115,28517,310],{"emptyLinePlaceholder":309},[115,28519,28520,28522,28524],{"class":117,"line":149},[115,28521,8808],{"class":121},[115,28523,18620],{"class":262},[115,28525,17271],{"class":125},[115,28527,28528,28530,28532,28534,28536,28538],{"class":117,"line":162},[115,28529,3822],{"class":121},[115,28531,18629],{"class":125},[115,28533,18632],{"class":132},[115,28535,2513],{"class":125},[115,28537,17281],{"class":132},[115,28539,18639],{"class":125},[16,28541,28542],{},"Check it locally on the server:",[106,28544,28546],{"className":108,"code":28545,"language":110,"meta":111,"style":111},"curl --fail --silent http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\n",[20,28547,28548],{"__ignoreMap":111},[115,28549,28550,28552,28555,28558],{"class":117,"line":118},[115,28551,2764],{"class":262},[115,28553,28554],{"class":202}," --fail",[115,28556,28557],{"class":202}," --silent",[115,28559,28560],{"class":132}," http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\n",[52,28562,28564],{"id":28563},"verify-the-reverse-proxy-path-too","Verify the reverse proxy path too",[16,28566,28567],{},"If Nginx is serving the public app, also verify through the externally served path or local vhost routing so you confirm the full request path is working, not just Gunicorn behind the proxy.",[16,28569,12414],{},[106,28571,28573],{"className":108,"code":28572,"language":110,"meta":111,"style":111},"curl --fail --silent https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,28574,28575],{"__ignoreMap":111},[115,28576,28577,28579,28581,28583],{"class":117,"line":118},[115,28578,2764],{"class":262},[115,28580,28554],{"class":202},[115,28582,28557],{"class":202},[115,28584,13426],{"class":132},[16,28586,28587],{},"Or from the server with the correct host header if needed:",[106,28589,28591],{"className":108,"code":28590,"language":110,"meta":111,"style":111},"curl --fail --silent -H \"Host: example.com\" http:\u002F\u002F127.0.0.1\u002Fhealth\u002F\n",[20,28592,28593],{"__ignoreMap":111},[115,28594,28595,28597,28599,28601,28603,28605],{"class":117,"line":118},[115,28596,2764],{"class":262},[115,28598,28554],{"class":202},[115,28600,28557],{"class":202},[115,28602,3939],{"class":202},[115,28604,3942],{"class":132},[115,28606,28607],{"class":132}," http:\u002F\u002F127.0.0.1\u002Fhealth\u002F\n",[52,28609,28611],{"id":28610},"review-recent-logs","Review recent logs",[106,28613,28614],{"className":108,"code":23806,"language":110,"meta":111,"style":111},[20,28615,28616],{"__ignoreMap":111},[115,28617,28618,28620,28622,28624,28626,28628],{"class":117,"line":118},[115,28619,2785],{"class":262},[115,28621,2788],{"class":202},[115,28623,2791],{"class":132},[115,28625,2794],{"class":202},[115,28627,15523],{"class":202},[115,28629,2800],{"class":202},[16,28631,28632],{},"Look for import errors, missing environment variables, database connection failures, or static path mistakes.",[11,28634,28636],{"id":28635},"add-rollback-protection-to-the-github-actions-deployment-flow","Add rollback protection to the GitHub Actions deployment flow",[16,28638,28639,28640,28643],{},"Keep previous releases in ",[20,28641,28642],{},"\u002Fsrv\u002Fmyapp\u002Freleases",". If the new release fails after the symlink switch, roll back to the prior release:",[106,28645,28647],{"className":108,"code":28646,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260423-184500 \u002Fsrv\u002Fmyapp\u002Fcurrent\nsudo systemctl restart gunicorn\n",[20,28648,28649,28660],{"__ignoreMap":111},[115,28650,28651,28653,28655,28658],{"class":117,"line":118},[115,28652,14854],{"class":262},[115,28654,14857],{"class":202},[115,28656,28657],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260423-184500",[115,28659,5306],{"class":132},[115,28661,28662,28664,28666,28668],{"class":117,"line":136},[115,28663,2001],{"class":262},[115,28665,3480],{"class":132},[115,28667,3483],{"class":132},[115,28669,1987],{"class":132},[16,28671,28672],{},"Automatic rollback is reasonable when:",[63,28674,28675,28678],{},[66,28676,28677],{},"Gunicorn fails to start",[66,28679,28680],{},"the health endpoint returns non-200",[16,28682,28683],{},"Manual rollback is safer when:",[63,28685,28686,28689],{},[66,28687,28688],{},"migrations may have changed data irreversibly",[66,28690,28691],{},"the failure affects background workers or external integrations",[16,28693,28694,28695,28697],{},"If all releases share one virtualenv, switching ",[20,28696,13654],{}," back to an older release may not fully restore service when newer dependencies were already installed. For stronger rollback isolation, use per-release virtualenvs or deploy a prebuilt artifact or runtime.",[16,28699,28700,28701,211],{},"If migrations are not backward compatible, code rollback alone may not restore service. In that case you may need a database restore or a forward-fix migration instead of a simple symlink rollback. Keep a tested rollback runbook before enabling automatic production deploys. For a deeper recovery process, see ",[1395,28702,17929],{"href":1415},[11,28704,1321],{"id":1320},[16,28706,28707],{},"This setup works because it separates deployment into three concerns:",[1173,28709,28710,28716,28722],{},[66,28711,28712,28715],{},[1226,28713,28714],{},"CI orchestration"," in GitHub Actions",[66,28717,28718,28721],{},[1226,28719,28720],{},"runtime configuration"," on the server",[66,28723,28724,28727,28728,28730],{},[1226,28725,28726],{},"release switching"," through versioned directories and a ",[20,28729,13654],{}," symlink",[16,28732,28733],{},"That gives you repeatable deploys without storing production secrets in the repository or rebuilding the whole server each time.",[16,28735,28736],{},"SSH-based deployment is a good fit for:",[63,28738,28739,28742,28745],{},[66,28740,28741],{},"a single VPS",[66,28743,28744],{},"small teams",[66,28746,28747],{},"existing Gunicorn + Nginx + systemd setups",[16,28749,28750],{},"An artifact-based deploy is better when you want tighter control over what gets released. A Docker-based workflow is better when your production runtime already uses containers.",[52,28752,28754],{"id":28753},"when-manual-github-actions-workflows-become-repetitive","When manual GitHub Actions workflows become repetitive",[16,28756,28757,28758,28760],{},"Once you repeat this process across multiple Django projects, the release steps should move into a reusable shell script or workflow template. The first things worth standardizing are release directory creation, migration and ",[20,28759,13689],{}," ordering, health checks, sudo-safe service restarts, and rollback commands. That reduces small differences between projects that often cause production mistakes.",[11,28762,1337],{"id":1336},[63,28764,28765,28771,28776,28782,28790,28795,28801,28807],{},[66,28766,28767,28770],{},[1226,28768,28769],{},"Single VPS:"," this workflow is enough for many production apps if backups, monitoring, and rollback are in place.",[66,28772,28773,28775],{},[1226,28774,4397],{}," replace the virtualenv and systemd steps with image build, image pull, and container restart logic.",[66,28777,28778,28781],{},[1226,28779,28780],{},"Long-running migrations:"," avoid blocking deploys with schema changes that lock large tables. Split migrations when needed.",[66,28783,28784,28786,28787,28789],{},[1226,28785,27007],{}," do not store user-uploaded media inside release directories. Keep media in ",[20,28788,23134],{}," or external object storage.",[66,28791,28792,28794],{},[1226,28793,10126],{}," make sure your web server serves static files from a stable location that still works after release switches and rollbacks.",[66,28796,28797,28800],{},[1226,28798,28799],{},"Timeouts and restarts:"," if Gunicorn startup is slow, systemd may mark the service failed. Check service timeouts and logs before assuming the workflow is broken.",[66,28802,28803,28806],{},[1226,28804,28805],{},"Proxy headers and TLS:"," if Nginx terminates TLS, make sure Django proxy settings, secure cookies, and HTTPS redirect behavior are already correct before automating deploys.",[66,28808,28809,28812],{},[1226,28810,28811],{},"Partial deploys:"," if dependency install or migration fails before the symlink switch, the current release stays active. That is another reason to avoid in-place updates.",[11,28814,1386],{"id":1385},[16,28816,28817,28818,211],{},"Before automating releases, review a full ",[1395,28819,3000],{"href":2999},[16,28821,28822,28823,28825],{},"If your app server and reverse proxy are not fully stable yet, set up ",[1395,28824,2986],{"href":2985}," first.",[16,28827,28828,28829,211],{},"If you are deploying an ASGI app instead of WSGI, see ",[1395,28830,8039],{"href":8038},[16,28832,28833,28834,211],{},"If you want a simpler HTTPS reverse proxy setup, see ",[1395,28835,8046],{"href":8045},[16,28837,28838,28839,211],{},"For recovery procedures, keep a tested ",[1395,28840,17929],{"href":1415},[11,28842,1420],{"id":1419},[52,28844,28846,28847,28849],{"id":28845},"should-github-actions-run-migrate-on-every-deploy","Should GitHub Actions run ",[20,28848,10296],{}," on every deploy?",[16,28851,28852],{},"Usually yes, if your deployment process assumes schema and code move together. But only do this when migrations are non-interactive, tested, and compatible with your rollback plan.",[52,28854,28856],{"id":28855},"is-ssh-based-deployment-secure-enough-for-django-production","Is SSH-based deployment secure enough for Django production?",[16,28858,28859],{},"Yes, if you use a non-root deploy user, restrict deploy triggers, pin the server host key, store the private key in GitHub secrets or environment secrets, and keep runtime application secrets on the server instead of in CI.",[52,28861,28863],{"id":28862},"should-i-deploy-from-source-or-from-a-built-artifact","Should I deploy from source or from a built artifact?",[16,28865,28866],{},"For a small VPS, a tarball upload is usually fine. Built artifacts are better when you want a stricter, repeatable release package and less variation in what reaches production.",[52,28868,28870],{"id":28869},"how-do-i-roll-back-a-failed-django-github-actions-deployment","How do I roll back a failed Django GitHub Actions deployment?",[16,28872,28873,28874,28876],{},"Point ",[20,28875,14814],{}," back to the previous release and restart Gunicorn. If the release included backward-incompatible migrations, or if a shared virtualenv introduced incompatible dependency changes, you may also need a database restore, dependency recovery, or a forward-fix deployment rather than a simple code rollback.",[1485,28878,28879],{},"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 .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":28881},[28882,28883,28884,28885,28890,28898,28902,28903,28912,28918,28919,28922,28923,28924],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":27218,"depth":136,"text":27219,"children":28886},[28887,28888,28889],{"id":27225,"depth":149,"text":27226},{"id":27232,"depth":149,"text":27233},{"id":27239,"depth":149,"text":27240},{"id":27261,"depth":136,"text":27262,"children":28891},[28892,28893,28894,28895,28896,28897],{"id":27268,"depth":149,"text":27269},{"id":27370,"depth":149,"text":27371},{"id":27418,"depth":149,"text":27419},{"id":27467,"depth":149,"text":27468},{"id":27519,"depth":149,"text":27520},{"id":27562,"depth":149,"text":27563},{"id":27693,"depth":136,"text":27694,"children":28899},[28900,28901],{"id":27745,"depth":149,"text":27746},{"id":27752,"depth":149,"text":27753},{"id":27759,"depth":136,"text":27760},{"id":28307,"depth":136,"text":28308,"children":28904},[28905,28906,28907,28908,28909,28910,28911],{"id":28314,"depth":149,"text":28315},{"id":28321,"depth":149,"text":28322},{"id":28332,"depth":149,"text":28333},{"id":28358,"depth":149,"text":28359},{"id":28377,"depth":149,"text":28378},{"id":28403,"depth":149,"text":28404},{"id":28434,"depth":149,"text":28435},{"id":28463,"depth":136,"text":28464,"children":28913},[28914,28915,28916,28917],{"id":28470,"depth":149,"text":28471},{"id":28491,"depth":149,"text":28492},{"id":28563,"depth":149,"text":28564},{"id":28610,"depth":149,"text":28611},{"id":28635,"depth":136,"text":28636},{"id":1320,"depth":136,"text":1321,"children":28920},[28921],{"id":28753,"depth":149,"text":28754},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":28925},[28926,28928,28929,28930],{"id":28845,"depth":149,"text":28927},"Should GitHub Actions run migrate on every deploy?",{"id":28855,"depth":149,"text":28856},{"id":28862,"depth":149,"text":28863},{"id":28869,"depth":149,"text":28870},"Manual Django deployments usually fail in predictable ways: someone forgets to run migrations, static files are outdated, the wrong branch gets pushed to production, or a restar...",{},"\u002Fdeploy-django-with-github-actions-cicd","24",[2985,8038,8045],{"title":27139,"description":28931},[1557,28938,28939],"github-actions","ci-cd","deploy-django-with-github-actions-cicd",[1557,28938,28939],"yinGT2VNtWcuP44_9WdmWx_HASzi1yF6T3n03pOp3dE",{"id":28944,"title":2986,"body":28945,"category":3088,"description":30680,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":30681,"navigation":309,"path":30682,"priority":4549,"related":30683,"role":1553,"section":3098,"seo":30684,"stack":30685,"stem":30687,"tags":30688,"type":1561,"__hash__":30689},"articles\u002Fdeploy-django-gunicorn-nginx-ubuntu.md",{"type":8,"value":28946,"toc":30655},[28947,28949,28952,28958,28960,28963,29006,29009,29011,29015,29018,29023,29034,29037,29043,29046,29050,29053,29099,29106,29109,29158,29161,29203,29205,29222,29226,29229,29304,29307,29388,29391,29552,29558,29561,29625,29627,29645,29651,29653,29656,29662,29781,29787,29789,29831,29833,29854,29856,29882,29888,29890,29893,30015,30018,30053,30056,30089,30092,30114,30117,30151,30154,30166,30169,30173,30176,30221,30224,30227,30240,30245,30248,30260,30264,30267,30286,30289,30355,30358,30380,30386,30388,30391,30410,30413,30433,30435,30507,30510,30512,30518,30521,30523,30529,30533,30571,30573,30598,30600,30604,30610,30614,30623,30627,30638,30642,30645,30649,30652],[11,28948,14],{"id":13},[16,28950,28951],{},"Moving a Django app from local development to production usually fails on the same points: process management, reverse proxy configuration, static files, secrets, TLS, and safe restarts. The Django development server is not suitable for public traffic, and a production deployment needs clear separation between the web server, app server, and application settings.",[16,28953,28954,28955,28957],{},"This guide shows a practical way to deploy Django with Gunicorn and Nginx on Ubuntu. It covers a single-server production pattern using ",[20,28956,1277],{}," for process supervision, Nginx for reverse proxy and static files, and Gunicorn for running Django. It does not cover container orchestration or a full server hardening baseline.",[11,28959,30],{"id":29},[16,28961,28962],{},"To deploy Django with Gunicorn and Nginx on Ubuntu:",[1173,28964,28965,28968,28971,28974,28977,28980,28986,28993,28996],{},[66,28966,28967],{},"install Ubuntu packages and Nginx",[66,28969,28970],{},"create a non-root deploy user and app directories",[66,28972,28973],{},"create a Python virtual environment and install dependencies",[66,28975,28976],{},"configure Django production settings with environment variables",[66,28978,28979],{},"run migrations and collect static files",[66,28981,28982,28983,28985],{},"create a ",[20,28984,1277],{}," Gunicorn service, usually bound to a Unix socket",[66,28987,28988,28989,3146,28991],{},"configure Nginx to proxy requests to Gunicorn and serve ",[20,28990,11729],{},[20,28992,13085],{},[66,28994,28995],{},"enable HTTPS with Let's Encrypt",[66,28997,28998,28999,1153,29001,1153,29003,29005],{},"verify with ",[20,29000,2764],{},[20,29002,1981],{},[20,29004,2785],{},", and Nginx logs",[16,29007,29008],{},"For safer rollback, keep the previous known-good commit, dependency set, and database backup or migration plan available until the new release is verified.",[11,29010,43],{"id":42},[11,29012,29014],{"id":29013},"architecture-for-django-gunicorn-nginx-on-ubuntu","Architecture for Django + Gunicorn + Nginx on Ubuntu",[16,29016,29017],{},"Request flow:",[16,29019,29020],{},[1226,29021,29022],{},"Browser → Nginx → Gunicorn → Django",[63,29024,29025,29028,29031],{},[66,29026,29027],{},"Nginx terminates HTTP\u002FHTTPS and serves static files directly.",[66,29029,29030],{},"Gunicorn runs the Django WSGI app.",[66,29032,29033],{},"Django talks to PostgreSQL and Redis if your app uses them.",[16,29035,29036],{},"A simple directory layout is enough:",[106,29038,29041],{"className":29039,"code":29040,"language":247,"meta":111},[245],"\u002Fopt\u002Fmyapp\u002F\n├── current\u002F              # active code checkout\n├── shared\u002F\n│   ├── .env\n│   ├── static\u002F\n│   └── media\u002F\n└── venv\u002F\n",[20,29042,29040],{"__ignoreMap":111},[16,29044,29045],{},"This stack is a good fit for a single VM, small teams, and non-container deployments.",[11,29047,29049],{"id":29048},"prepare-the-ubuntu-server","Prepare the Ubuntu server",[16,29051,29052],{},"Install base packages:",[106,29054,29056],{"className":108,"code":29055,"language":110,"meta":111,"style":111},"sudo apt update\nsudo apt install -y python3-venv python3-pip python3-dev build-essential \\\n    libpq-dev nginx git ufw\n",[20,29057,29058,29066,29087],{"__ignoreMap":111},[115,29059,29060,29062,29064],{"class":117,"line":118},[115,29061,2001],{"class":262},[115,29063,6588],{"class":132},[115,29065,6591],{"class":132},[115,29067,29068,29070,29072,29074,29076,29078,29080,29083,29085],{"class":117,"line":136},[115,29069,2001],{"class":262},[115,29071,6588],{"class":132},[115,29073,6600],{"class":132},[115,29075,8432],{"class":202},[115,29077,14477],{"class":132},[115,29079,14480],{"class":132},[115,29081,29082],{"class":132}," python3-dev",[115,29084,14493],{"class":132},[115,29086,317],{"class":202},[115,29088,29089,29092,29094,29096],{"class":117,"line":149},[115,29090,29091],{"class":132},"    libpq-dev",[115,29093,3906],{"class":132},[115,29095,14490],{"class":132},[115,29097,29098],{"class":132}," ufw\n",[16,29100,29101,29102,29105],{},"If PostgreSQL is remote, ",[20,29103,29104],{},"libpq-dev"," is still useful when building PostgreSQL client dependencies.",[16,29107,29108],{},"Create a deploy user and app directories:",[106,29110,29112],{"className":108,"code":29111,"language":110,"meta":111,"style":111},"sudo adduser --disabled-password --gecos \"\" deploy\nsudo mkdir -p \u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic \u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\nsudo chown -R deploy:deploy \u002Fopt\u002Fmyapp\n",[20,29113,29114,29131,29145],{"__ignoreMap":111},[115,29115,29116,29118,29120,29123,29126,29129],{"class":117,"line":118},[115,29117,2001],{"class":262},[115,29119,14212],{"class":132},[115,29121,29122],{"class":202}," --disabled-password",[115,29124,29125],{"class":202}," --gecos",[115,29127,29128],{"class":132}," \"\"",[115,29130,14215],{"class":132},[115,29132,29133,29135,29137,29139,29142],{"class":117,"line":136},[115,29134,2001],{"class":262},[115,29136,6721],{"class":132},[115,29138,1001],{"class":202},[115,29140,29141],{"class":132}," \u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic",[115,29143,29144],{"class":132}," \u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\n",[115,29146,29147,29149,29151,29153,29155],{"class":117,"line":149},[115,29148,2001],{"class":262},[115,29150,6733],{"class":132},[115,29152,6736],{"class":202},[115,29154,14264],{"class":132},[115,29156,29157],{"class":132}," \u002Fopt\u002Fmyapp\n",[16,29159,29160],{},"Basic firewall rules:",[106,29162,29164],{"className":108,"code":29163,"language":110,"meta":111,"style":111},"sudo ufw allow OpenSSH\nsudo ufw allow 'Nginx Full'\nsudo ufw enable\nsudo ufw status\n",[20,29165,29166,29176,29187,29195],{"__ignoreMap":111},[115,29167,29168,29170,29172,29174],{"class":117,"line":118},[115,29169,2001],{"class":262},[115,29171,2014],{"class":132},[115,29173,14341],{"class":132},[115,29175,14344],{"class":132},[115,29177,29178,29180,29182,29184],{"class":117,"line":136},[115,29179,2001],{"class":262},[115,29181,2014],{"class":132},[115,29183,14341],{"class":132},[115,29185,29186],{"class":132}," 'Nginx Full'\n",[115,29188,29189,29191,29193],{"class":117,"line":149},[115,29190,2001],{"class":262},[115,29192,2014],{"class":132},[115,29194,14375],{"class":132},[115,29196,29197,29199,29201],{"class":117,"line":162},[115,29198,2001],{"class":262},[115,29200,2014],{"class":132},[115,29202,2017],{"class":132},[16,29204,8572],{},[106,29206,29208],{"className":108,"code":29207,"language":110,"meta":111,"style":111},"nginx -v\npython3 --version\n",[20,29209,29210,29216],{"__ignoreMap":111},[115,29211,29212,29214],{"class":117,"line":118},[115,29213,2156],{"class":262},[115,29215,14568],{"class":202},[115,29217,29218,29220],{"class":117,"line":136},[115,29219,12281],{"class":262},[115,29221,6614],{"class":202},[11,29223,29225],{"id":29224},"prepare-the-django-application-for-production","Prepare the Django application for production",[16,29227,29228],{},"Get the code onto the server as the deploy user:",[106,29230,29232],{"className":108,"code":29231,"language":110,"meta":111,"style":111},"sudo -u deploy -H bash\ncd \u002Fopt\u002Fmyapp\ngit clone https:\u002F\u002Fexample.com\u002Fyour-repo.git current\npython3 -m venv \u002Fopt\u002Fmyapp\u002Fvenv\nsource \u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install --upgrade pip\npip install -r \u002Fopt\u002Fmyapp\u002Fcurrent\u002Frequirements.txt\n",[20,29233,29234,29247,29253,29265,29276,29283,29293],{"__ignoreMap":111},[115,29235,29236,29238,29240,29242,29244],{"class":117,"line":118},[115,29237,2001],{"class":262},[115,29239,2788],{"class":202},[115,29241,19270],{"class":132},[115,29243,3939],{"class":202},[115,29245,29246],{"class":132}," bash\n",[115,29248,29249,29251],{"class":117,"line":136},[115,29250,5303],{"class":202},[115,29252,29157],{"class":132},[115,29254,29255,29257,29259,29262],{"class":117,"line":149},[115,29256,13525],{"class":262},[115,29258,14843],{"class":132},[115,29260,29261],{"class":132}," https:\u002F\u002Fexample.com\u002Fyour-repo.git",[115,29263,29264],{"class":132}," current\n",[115,29266,29267,29269,29271,29273],{"class":117,"line":162},[115,29268,12281],{"class":262},[115,29270,12284],{"class":202},[115,29272,12287],{"class":132},[115,29274,29275],{"class":132}," \u002Fopt\u002Fmyapp\u002Fvenv\n",[115,29277,29278,29280],{"class":117,"line":175},[115,29279,5311],{"class":202},[115,29281,29282],{"class":132}," \u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\n",[115,29284,29285,29287,29289,29291],{"class":117,"line":350},[115,29286,8618],{"class":262},[115,29288,6600],{"class":132},[115,29290,12338],{"class":202},[115,29292,12341],{"class":132},[115,29294,29295,29297,29299,29301],{"class":117,"line":365},[115,29296,8618],{"class":262},[115,29298,6600],{"class":132},[115,29300,12350],{"class":202},[115,29302,29303],{"class":132}," \u002Fopt\u002Fmyapp\u002Fcurrent\u002Frequirements.txt\n",[16,29305,29306],{},"Create an environment file outside the repository:",[106,29308,29310],{"className":108,"code":29309,"language":110,"meta":111,"style":111},"sudo tee \u002Fopt\u002Fmyapp\u002Fshared\u002F.env > \u002Fdev\u002Fnull \u003C\u003C'EOF'\nDJANGO_SECRET_KEY=replace-me\nDJANGO_DEBUG=False\nDJANGO_ALLOWED_HOSTS=example.com,www.example.com\nDJANGO_CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\nDATABASE_URL=postgres:\u002F\u002Fuser:pass@dbhost:5432\u002Fmydb\nREDIS_URL=redis:\u002F\u002F127.0.0.1:6379\u002F1\nEOF\n\nsudo chown deploy:deploy \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\nsudo chmod 600 \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\n",[20,29311,29312,29332,29336,29340,29344,29348,29353,29358,29363,29367,29378],{"__ignoreMap":111},[115,29313,29314,29316,29319,29322,29324,29326,29329],{"class":117,"line":118},[115,29315,2001],{"class":262},[115,29317,29318],{"class":132}," tee",[115,29320,29321],{"class":132}," \u002Fopt\u002Fmyapp\u002Fshared\u002F.env",[115,29323,604],{"class":121},[115,29325,4015],{"class":132},[115,29327,29328],{"class":121}," \u003C\u003C",[115,29330,29331],{"class":132},"'EOF'\n",[115,29333,29334],{"class":117,"line":136},[115,29335,12429],{"class":132},[115,29337,29338],{"class":117,"line":149},[115,29339,15015],{"class":132},[115,29341,29342],{"class":117,"line":162},[115,29343,15020],{"class":132},[115,29345,29346],{"class":117,"line":175},[115,29347,15025],{"class":132},[115,29349,29350],{"class":117,"line":350},[115,29351,29352],{"class":132},"DATABASE_URL=postgres:\u002F\u002Fuser:pass@dbhost:5432\u002Fmydb\n",[115,29354,29355],{"class":117,"line":365},[115,29356,29357],{"class":132},"REDIS_URL=redis:\u002F\u002F127.0.0.1:6379\u002F1\n",[115,29359,29360],{"class":117,"line":380},[115,29361,29362],{"class":132},"EOF\n",[115,29364,29365],{"class":117,"line":487},[115,29366,310],{"emptyLinePlaceholder":309},[115,29368,29369,29371,29373,29375],{"class":117,"line":2095},[115,29370,2001],{"class":262},[115,29372,6733],{"class":132},[115,29374,14264],{"class":132},[115,29376,29377],{"class":132}," \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\n",[115,29379,29380,29382,29384,29386],{"class":117,"line":2104},[115,29381,2001],{"class":262},[115,29383,12480],{"class":132},[115,29385,266],{"class":202},[115,29387,29377],{"class":132},[16,29389,29390],{},"Your Django settings must read these values from the environment. Production settings should include at least:",[106,29392,29394],{"className":2369,"code":29393,"language":1114,"meta":111,"style":111},"import os\n\nDEBUG = False\nALLOWED_HOSTS = [h for h in os.environ[\"DJANGO_ALLOWED_HOSTS\"].split(\",\") if h]\nCSRF_TRUSTED_ORIGINS = [\n    u for u in os.environ.get(\"DJANGO_CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if u\n]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\nSTATIC_ROOT = \"\u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic\"\nMEDIA_ROOT = \"\u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\"\n",[20,29395,29396,29402,29406,29414,29443,29451,29482,29486,29490,29506,29514,29522,29530,29534,29543],{"__ignoreMap":111},[115,29397,29398,29400],{"class":117,"line":118},[115,29399,5613],{"class":121},[115,29401,5616],{"class":125},[115,29403,29404],{"class":117,"line":136},[115,29405,310],{"emptyLinePlaceholder":309},[115,29407,29408,29410,29412],{"class":117,"line":149},[115,29409,7350],{"class":202},[115,29411,2380],{"class":121},[115,29413,7355],{"class":202},[115,29415,29416,29418,29420,29422,29424,29426,29428,29430,29432,29435,29437,29439,29441],{"class":117,"line":162},[115,29417,2719],{"class":202},[115,29419,2380],{"class":121},[115,29421,25191],{"class":125},[115,29423,18256],{"class":121},[115,29425,18259],{"class":125},[115,29427,18262],{"class":121},[115,29429,8861],{"class":125},[115,29431,25202],{"class":132},[115,29433,29434],{"class":125},"].split(",[115,29436,18278],{"class":132},[115,29438,18281],{"class":125},[115,29440,10833],{"class":121},[115,29442,25217],{"class":125},[115,29444,29445,29447,29449],{"class":117,"line":175},[115,29446,2725],{"class":202},[115,29448,2380],{"class":121},[115,29450,3540],{"class":125},[115,29452,29453,29456,29458,29461,29463,29465,29467,29469,29471,29473,29475,29477,29479],{"class":117,"line":350},[115,29454,29455],{"class":125},"    u ",[115,29457,18256],{"class":121},[115,29459,29460],{"class":125}," u ",[115,29462,18262],{"class":121},[115,29464,8884],{"class":125},[115,29466,25237],{"class":132},[115,29468,1153],{"class":125},[115,29470,18272],{"class":132},[115,29472,18275],{"class":125},[115,29474,18278],{"class":132},[115,29476,18281],{"class":125},[115,29478,10833],{"class":121},[115,29480,29481],{"class":125}," u\n",[115,29483,29484],{"class":117,"line":365},[115,29485,2552],{"class":125},[115,29487,29488],{"class":117,"line":380},[115,29489,310],{"emptyLinePlaceholder":309},[115,29491,29492,29494,29496,29498,29500,29502,29504],{"class":117,"line":487},[115,29493,2377],{"class":202},[115,29495,2380],{"class":121},[115,29497,2383],{"class":125},[115,29499,2386],{"class":132},[115,29501,1153],{"class":125},[115,29503,2391],{"class":132},[115,29505,2394],{"class":125},[115,29507,29508,29510,29512],{"class":117,"line":2095},[115,29509,2407],{"class":202},[115,29511,2380],{"class":121},[115,29513,2412],{"class":202},[115,29515,29516,29518,29520],{"class":117,"line":2104},[115,29517,2417],{"class":202},[115,29519,2380],{"class":121},[115,29521,2412],{"class":202},[115,29523,29524,29526,29528],{"class":117,"line":2113},[115,29525,2426],{"class":202},[115,29527,2380],{"class":121},[115,29529,2412],{"class":202},[115,29531,29532],{"class":117,"line":2122},[115,29533,310],{"emptyLinePlaceholder":309},[115,29535,29536,29538,29540],{"class":117,"line":2131},[115,29537,11918],{"class":202},[115,29539,2380],{"class":121},[115,29541,29542],{"class":132}," \"\u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic\"\n",[115,29544,29545,29547,29549],{"class":117,"line":2136},[115,29546,15214],{"class":202},[115,29548,2380],{"class":121},[115,29550,29551],{"class":132}," \"\u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\"\n",[16,29553,29554,29555,29557],{},"If you prefer to handle HTTP-to-HTTPS redirects only in Nginx, do that consistently and do not enable ",[20,29556,2407],{},". In this guide, Django is configured to treat proxied HTTPS requests as secure and redirect HTTP to HTTPS.",[16,29559,29560],{},"Run checks, migrations, and static collection:",[106,29562,29564],{"className":108,"code":29563,"language":110,"meta":111,"style":111},"cd \u002Fopt\u002Fmyapp\u002Fcurrent\nsource \u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\nset -a\n. \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\nset +a\npython manage.py check --deploy\npython manage.py migrate\npython manage.py collectstatic --noinput\n",[20,29565,29566,29573,29579,29585,29591,29597,29607,29615],{"__ignoreMap":111},[115,29567,29568,29570],{"class":117,"line":118},[115,29569,5303],{"class":202},[115,29571,29572],{"class":132}," \u002Fopt\u002Fmyapp\u002Fcurrent\n",[115,29574,29575,29577],{"class":117,"line":136},[115,29576,5311],{"class":202},[115,29578,29282],{"class":132},[115,29580,29581,29583],{"class":117,"line":149},[115,29582,203],{"class":202},[115,29584,206],{"class":202},[115,29586,29587,29589],{"class":117,"line":162},[115,29588,211],{"class":202},[115,29590,29377],{"class":132},[115,29592,29593,29595],{"class":117,"line":175},[115,29594,203],{"class":202},[115,29596,221],{"class":132},[115,29598,29599,29601,29603,29605],{"class":117,"line":350},[115,29600,1114],{"class":262},[115,29602,1117],{"class":132},[115,29604,1814],{"class":132},[115,29606,1817],{"class":202},[115,29608,29609,29611,29613],{"class":117,"line":365},[115,29610,1114],{"class":262},[115,29612,1117],{"class":132},[115,29614,11324],{"class":132},[115,29616,29617,29619,29621,29623],{"class":117,"line":380},[115,29618,1114],{"class":262},[115,29620,1117],{"class":132},[115,29622,1838],{"class":132},[115,29624,1841],{"class":202},[16,29626,8572],{},[106,29628,29630],{"className":108,"code":29629,"language":110,"meta":111,"style":111},"ls -la \u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic | head\n",[20,29631,29632],{"__ignoreMap":111},[115,29633,29634,29636,29639,29641,29643],{"class":117,"line":118},[115,29635,532],{"class":262},[115,29637,29638],{"class":202}," -la",[115,29640,29141],{"class":132},[115,29642,579],{"class":121},[115,29644,582],{"class":262},[16,29646,29647,29648,29650],{},"Before running ",[20,29649,10296],{}," on an important production system, make sure you have a recent backup and a clear rollback plan for schema changes. If migrations are destructive, code rollback alone may not be enough.",[11,29652,14116],{"id":21876},[16,29654,29655],{},"For Nginx on the same server, a Unix socket is a good default.",[16,29657,29658,29659,29661],{},"Create a ",[20,29660,1277],{}," service:",[106,29663,29665],{"className":108,"code":29664,"language":110,"meta":111,"style":111},"sudo tee \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-myapp.service > \u002Fdev\u002Fnull \u003C\u003C'EOF'\n[Unit]\nDescription=Gunicorn for myapp\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fopt\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fopt\u002Fmyapp\u002Fshared\u002F.env\nRuntimeDirectory=gunicorn-myapp\nRuntimeDirectoryMode=0755\nExecStart=\u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn \\\n    --workers 3 \\\n    --bind unix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock \\\n    myproject.wsgi:application\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\nEOF\n",[20,29666,29667,29684,29688,29693,29698,29702,29706,29711,29716,29721,29726,29731,29736,29741,29745,29749,29754,29759,29764,29768,29772,29777],{"__ignoreMap":111},[115,29668,29669,29671,29673,29676,29678,29680,29682],{"class":117,"line":118},[115,29670,2001],{"class":262},[115,29672,29318],{"class":132},[115,29674,29675],{"class":132}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-myapp.service",[115,29677,604],{"class":121},[115,29679,4015],{"class":132},[115,29681,29328],{"class":121},[115,29683,29331],{"class":132},[115,29685,29686],{"class":117,"line":136},[115,29687,2035],{"class":132},[115,29689,29690],{"class":117,"line":149},[115,29691,29692],{"class":132},"Description=Gunicorn for myapp\n",[115,29694,29695],{"class":117,"line":162},[115,29696,29697],{"class":132},"After=network.target\n",[115,29699,29700],{"class":117,"line":175},[115,29701,310],{"emptyLinePlaceholder":309},[115,29703,29704],{"class":117,"line":350},[115,29705,2060],{"class":132},[115,29707,29708],{"class":117,"line":365},[115,29709,29710],{"class":132},"User=deploy\n",[115,29712,29713],{"class":117,"line":380},[115,29714,29715],{"class":132},"Group=www-data\n",[115,29717,29718],{"class":117,"line":487},[115,29719,29720],{"class":132},"WorkingDirectory=\u002Fopt\u002Fmyapp\u002Fcurrent\n",[115,29722,29723],{"class":117,"line":2095},[115,29724,29725],{"class":132},"EnvironmentFile=\u002Fopt\u002Fmyapp\u002Fshared\u002F.env\n",[115,29727,29728],{"class":117,"line":2104},[115,29729,29730],{"class":132},"RuntimeDirectory=gunicorn-myapp\n",[115,29732,29733],{"class":117,"line":2113},[115,29734,29735],{"class":132},"RuntimeDirectoryMode=0755\n",[115,29737,29738],{"class":117,"line":2122},[115,29739,29740],{"class":132},"ExecStart=\u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn \\\n",[115,29742,29743],{"class":117,"line":2131},[115,29744,15417],{"class":132},[115,29746,29747],{"class":117,"line":2136},[115,29748,15422],{"class":132},[115,29750,29751],{"class":117,"line":2142},[115,29752,29753],{"class":132},"    myproject.wsgi:application\n",[115,29755,29756],{"class":117,"line":2273},[115,29757,29758],{"class":132},"Restart=always\n",[115,29760,29761],{"class":117,"line":2282},[115,29762,29763],{"class":132},"RestartSec=5\n",[115,29765,29766],{"class":117,"line":2291},[115,29767,310],{"emptyLinePlaceholder":309},[115,29769,29770],{"class":117,"line":2299},[115,29771,2139],{"class":132},[115,29773,29774],{"class":117,"line":2307},[115,29775,29776],{"class":132},"WantedBy=multi-user.target\n",[115,29778,29779],{"class":117,"line":2315},[115,29780,29362],{"class":132},[16,29782,12628,29783,29786],{},[20,29784,29785],{},"myproject.wsgi:application"," with your actual Django WSGI path.",[16,29788,15456],{},[106,29790,29791],{"className":108,"code":15459,"language":110,"meta":111,"style":111},[20,29792,29793,29801,29811,29821],{"__ignoreMap":111},[115,29794,29795,29797,29799],{"class":117,"line":118},[115,29796,2001],{"class":262},[115,29798,3480],{"class":132},[115,29800,4984],{"class":132},[115,29802,29803,29805,29807,29809],{"class":117,"line":136},[115,29804,2001],{"class":262},[115,29806,3480],{"class":132},[115,29808,8567],{"class":132},[115,29810,15480],{"class":132},[115,29812,29813,29815,29817,29819],{"class":117,"line":149},[115,29814,2001],{"class":262},[115,29816,3480],{"class":132},[115,29818,15489],{"class":132},[115,29820,15480],{"class":132},[115,29822,29823,29825,29827,29829],{"class":117,"line":162},[115,29824,2001],{"class":262},[115,29826,3480],{"class":132},[115,29828,1984],{"class":132},[115,29830,15480],{"class":132},[16,29832,12724],{},[106,29834,29836],{"className":108,"code":29835,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn-myapp -n 50 --no-pager\n",[20,29837,29838],{"__ignoreMap":111},[115,29839,29840,29842,29844,29846,29848,29850,29852],{"class":117,"line":118},[115,29841,2001],{"class":262},[115,29843,5030],{"class":132},[115,29845,2788],{"class":202},[115,29847,15518],{"class":132},[115,29849,2794],{"class":202},[115,29851,15523],{"class":202},[115,29853,2800],{"class":202},[16,29855,8572],{},[106,29857,29859],{"className":108,"code":29858,"language":110,"meta":111,"style":111},"sudo ls -l \u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock\ncurl --unix-socket \u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock http:\u002F\u002Flocalhost\u002F\n",[20,29860,29861,29871],{"__ignoreMap":111},[115,29862,29863,29865,29867,29869],{"class":117,"line":118},[115,29864,2001],{"class":262},[115,29866,12758],{"class":132},[115,29868,14881],{"class":202},[115,29870,15545],{"class":132},[115,29872,29873,29875,29877,29880],{"class":117,"line":136},[115,29874,2764],{"class":262},[115,29876,13366],{"class":202},[115,29878,29879],{"class":132}," \u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock",[115,29881,13372],{"class":132},[16,29883,29884,29885,29887],{},"If Gunicorn fails here, do not continue to Nginx until ",[20,29886,23834],{}," is clean and the socket test works.",[11,29889,22172],{"id":22171},[16,29891,29892],{},"Create an Nginx server block:",[106,29894,29896],{"className":108,"code":29895,"language":110,"meta":111,"style":111},"sudo tee \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp > \u002Fdev\u002Fnull \u003C\u003C'EOF'\nserver {\n    listen 80;\n    server_name example.com www.example.com;\n\n    client_max_body_size 20M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        include proxy_params;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock:\u002F;\n    }\n}\nEOF\n",[20,29897,29898,29914,29919,29924,29929,29933,29938,29942,29947,29952,29956,29960,29965,29970,29974,29978,29983,29988,29993,29998,30003,30007,30011],{"__ignoreMap":111},[115,29899,29900,29902,29904,29906,29908,29910,29912],{"class":117,"line":118},[115,29901,2001],{"class":262},[115,29903,29318],{"class":132},[115,29905,15709],{"class":132},[115,29907,604],{"class":121},[115,29909,4015],{"class":132},[115,29911,29328],{"class":121},[115,29913,29331],{"class":132},[115,29915,29916],{"class":117,"line":136},[115,29917,29918],{"class":132},"server {\n",[115,29920,29921],{"class":117,"line":149},[115,29922,29923],{"class":132},"    listen 80;\n",[115,29925,29926],{"class":117,"line":162},[115,29927,29928],{"class":132},"    server_name example.com www.example.com;\n",[115,29930,29931],{"class":117,"line":175},[115,29932,310],{"emptyLinePlaceholder":309},[115,29934,29935],{"class":117,"line":350},[115,29936,29937],{"class":132},"    client_max_body_size 20M;\n",[115,29939,29940],{"class":117,"line":365},[115,29941,310],{"emptyLinePlaceholder":309},[115,29943,29944],{"class":117,"line":380},[115,29945,29946],{"class":132},"    location \u002Fstatic\u002F {\n",[115,29948,29949],{"class":117,"line":487},[115,29950,29951],{"class":132},"        alias \u002Fopt\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n",[115,29953,29954],{"class":117,"line":2095},[115,29955,2233],{"class":132},[115,29957,29958],{"class":117,"line":2104},[115,29959,310],{"emptyLinePlaceholder":309},[115,29961,29962],{"class":117,"line":2113},[115,29963,29964],{"class":132},"    location \u002Fmedia\u002F {\n",[115,29966,29967],{"class":117,"line":2122},[115,29968,29969],{"class":132},"        alias \u002Fopt\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n",[115,29971,29972],{"class":117,"line":2131},[115,29973,2233],{"class":132},[115,29975,29976],{"class":117,"line":2136},[115,29977,310],{"emptyLinePlaceholder":309},[115,29979,29980],{"class":117,"line":2142},[115,29981,29982],{"class":132},"    location \u002F {\n",[115,29984,29985],{"class":117,"line":2273},[115,29986,29987],{"class":132},"        include proxy_params;\n",[115,29989,29990],{"class":117,"line":2282},[115,29991,29992],{"class":132},"        proxy_set_header Host $host;\n",[115,29994,29995],{"class":117,"line":2291},[115,29996,29997],{"class":132},"        proxy_set_header X-Forwarded-Proto $scheme;\n",[115,29999,30000],{"class":117,"line":2299},[115,30001,30002],{"class":132},"        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp\u002Fgunicorn.sock:\u002F;\n",[115,30004,30005],{"class":117,"line":2307},[115,30006,2233],{"class":132},[115,30008,30009],{"class":117,"line":2315},[115,30010,2323],{"class":132},[115,30012,30013],{"class":117,"line":2320},[115,30014,29362],{"class":132},[16,30016,30017],{},"Enable the site and test the config:",[106,30019,30021],{"className":108,"code":30020,"language":110,"meta":111,"style":111},"sudo ln -s \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp \u002Fetc\u002Fnginx\u002Fsites-enabled\u002F\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,30022,30023,30035,30043],{"__ignoreMap":111},[115,30024,30025,30027,30029,30031,30033],{"class":117,"line":118},[115,30026,2001],{"class":262},[115,30028,13105],{"class":132},[115,30030,549],{"class":202},[115,30032,15709],{"class":132},[115,30034,15712],{"class":132},[115,30036,30037,30039,30041],{"class":117,"line":136},[115,30038,2001],{"class":262},[115,30040,3906],{"class":132},[115,30042,4282],{"class":202},[115,30044,30045,30047,30049,30051],{"class":117,"line":149},[115,30046,2001],{"class":262},[115,30048,3480],{"class":132},[115,30050,3919],{"class":132},[115,30052,1996],{"class":132},[16,30054,30055],{},"Remove the default site if needed:",[106,30057,30059],{"className":108,"code":30058,"language":110,"meta":111,"style":111},"sudo rm -f \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,30060,30061,30071,30079],{"__ignoreMap":111},[115,30062,30063,30065,30067,30069],{"class":117,"line":118},[115,30064,2001],{"class":262},[115,30066,15719],{"class":132},[115,30068,2777],{"class":202},[115,30070,15724],{"class":132},[115,30072,30073,30075,30077],{"class":117,"line":136},[115,30074,2001],{"class":262},[115,30076,3906],{"class":132},[115,30078,4282],{"class":202},[115,30080,30081,30083,30085,30087],{"class":117,"line":149},[115,30082,2001],{"class":262},[115,30084,3480],{"class":132},[115,30086,3919],{"class":132},[115,30088,1996],{"class":132},[16,30090,30091],{},"Basic verification:",[106,30093,30095],{"className":108,"code":30094,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\ncurl -I http:\u002F\u002Fexample.com\u002Fstatic\u002F\n",[20,30096,30097,30105],{"__ignoreMap":111},[115,30098,30099,30101,30103],{"class":117,"line":118},[115,30100,2764],{"class":262},[115,30102,2767],{"class":202},[115,30104,6494],{"class":132},[115,30106,30107,30109,30111],{"class":117,"line":136},[115,30108,2764],{"class":262},[115,30110,2767],{"class":202},[115,30112,30113],{"class":132}," http:\u002F\u002Fexample.com\u002Fstatic\u002F\n",[16,30115,30116],{},"Add TLS with Certbot:",[106,30118,30119],{"className":108,"code":15780,"language":110,"meta":111,"style":111},[20,30120,30121,30135],{"__ignoreMap":111},[115,30122,30123,30125,30127,30129,30131,30133],{"class":117,"line":118},[115,30124,2001],{"class":262},[115,30126,6588],{"class":132},[115,30128,6600],{"class":132},[115,30130,8432],{"class":202},[115,30132,6603],{"class":132},[115,30134,6606],{"class":132},[115,30136,30137,30139,30141,30143,30145,30147,30149],{"class":117,"line":136},[115,30138,2001],{"class":262},[115,30140,6603],{"class":132},[115,30142,6687],{"class":202},[115,30144,1019],{"class":202},[115,30146,6434],{"class":132},[115,30148,1019],{"class":202},[115,30150,6696],{"class":132},[16,30152,30153],{},"Then verify HTTPS:",[106,30155,30156],{"className":108,"code":13378,"language":110,"meta":111,"style":111},[20,30157,30158],{"__ignoreMap":111},[115,30159,30160,30162,30164],{"class":117,"line":118},[115,30161,2764],{"class":262},[115,30163,2767],{"class":202},[115,30165,2770],{"class":132},[16,30167,30168],{},"Only enable HSTS after HTTPS is working correctly for all relevant hostnames.",[11,30170,30172],{"id":30171},"security-checks-for-a-production-django-deployment","Security checks for a production Django deployment",[16,30174,30175],{},"Confirm that debug mode is off and deploy checks pass:",[106,30177,30179],{"className":108,"code":30178,"language":110,"meta":111,"style":111},"cd \u002Fopt\u002Fmyapp\u002Fcurrent\nsource \u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\nset -a\n. \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\nset +a\npython manage.py check --deploy\n",[20,30180,30181,30187,30193,30199,30205,30211],{"__ignoreMap":111},[115,30182,30183,30185],{"class":117,"line":118},[115,30184,5303],{"class":202},[115,30186,29572],{"class":132},[115,30188,30189,30191],{"class":117,"line":136},[115,30190,5311],{"class":202},[115,30192,29282],{"class":132},[115,30194,30195,30197],{"class":117,"line":149},[115,30196,203],{"class":202},[115,30198,206],{"class":202},[115,30200,30201,30203],{"class":117,"line":162},[115,30202,211],{"class":202},[115,30204,29377],{"class":132},[115,30206,30207,30209],{"class":117,"line":175},[115,30208,203],{"class":202},[115,30210,221],{"class":132},[115,30212,30213,30215,30217,30219],{"class":117,"line":350},[115,30214,1114],{"class":262},[115,30216,1117],{"class":132},[115,30218,1814],{"class":132},[115,30220,1817],{"class":202},[16,30222,30223],{},"If you trigger an error intentionally in a controlled way, Django should return a generic error page, not a debug traceback.",[16,30225,30226],{},"Keep secrets out of the repository and locked down on disk:",[106,30228,30230],{"className":108,"code":30229,"language":110,"meta":111,"style":111},"ls -l \u002Fopt\u002Fmyapp\u002Fshared\u002F.env\n",[20,30231,30232],{"__ignoreMap":111},[115,30233,30234,30236,30238],{"class":117,"line":118},[115,30235,532],{"class":262},[115,30237,14881],{"class":202},[115,30239,29377],{"class":132},[16,30241,30242,30243,211],{},"Expected permissions should be restrictive, such as ",[20,30244,16740],{},[16,30246,30247],{},"Also confirm that HTTPS is actually in use:",[106,30249,30250],{"className":108,"code":13378,"language":110,"meta":111,"style":111},[20,30251,30252],{"__ignoreMap":111},[115,30253,30254,30256,30258],{"class":117,"line":118},[115,30255,2764],{"class":262},[115,30257,2767],{"class":202},[115,30259,2770],{"class":132},[11,30261,30263],{"id":30262},"validate-the-deployment-end-to-end","Validate the deployment end to end",[16,30265,30266],{},"Browser and HTTP checks:",[63,30268,30269,30271,30273,30278,30281],{},[66,30270,24517],{},[66,30272,17744],{},[66,30274,30275,30276],{},"CSS and JS return ",[20,30277,17741],{},[66,30279,30280],{},"HTTP redirects to HTTPS after Certbot changes",[66,30282,30283,30284],{},"file uploads work if your app uses ",[20,30285,13085],{},[16,30287,30288],{},"Service and log checks:",[106,30290,30292],{"className":108,"code":30291,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn-myapp\nsudo systemctl status nginx\nsudo journalctl -u gunicorn-myapp -n 50 --no-pager\nsudo tail -n 50 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\nsudo tail -n 50 \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[20,30293,30294,30304,30314,30330,30342],{"__ignoreMap":111},[115,30295,30296,30298,30300,30302],{"class":117,"line":118},[115,30297,2001],{"class":262},[115,30299,3480],{"class":132},[115,30301,1984],{"class":132},[115,30303,15480],{"class":132},[115,30305,30306,30308,30310,30312],{"class":117,"line":136},[115,30307,2001],{"class":262},[115,30309,3480],{"class":132},[115,30311,1984],{"class":132},[115,30313,1996],{"class":132},[115,30315,30316,30318,30320,30322,30324,30326,30328],{"class":117,"line":149},[115,30317,2001],{"class":262},[115,30319,5030],{"class":132},[115,30321,2788],{"class":202},[115,30323,15518],{"class":132},[115,30325,2794],{"class":202},[115,30327,15523],{"class":202},[115,30329,2800],{"class":202},[115,30331,30332,30334,30336,30338,30340],{"class":117,"line":162},[115,30333,2001],{"class":262},[115,30335,13188],{"class":132},[115,30337,2794],{"class":202},[115,30339,15523],{"class":202},[115,30341,13195],{"class":132},[115,30343,30344,30346,30348,30350,30352],{"class":117,"line":175},[115,30345,2001],{"class":262},[115,30347,13188],{"class":132},[115,30349,2794],{"class":202},[115,30351,15523],{"class":202},[115,30353,30354],{"class":132}," \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[16,30356,30357],{},"A useful smoke test:",[106,30359,30361],{"className":108,"code":30360,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\ncurl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fpath-to-a-real-file.css\n",[20,30362,30363,30371],{"__ignoreMap":111},[115,30364,30365,30367,30369],{"class":117,"line":118},[115,30366,2764],{"class":262},[115,30368,2767],{"class":202},[115,30370,2770],{"class":132},[115,30372,30373,30375,30377],{"class":117,"line":136},[115,30374,2764],{"class":262},[115,30376,2767],{"class":202},[115,30378,30379],{"class":132}," https:\u002F\u002Fexample.com\u002Fstatic\u002Fpath-to-a-real-file.css\n",[16,30381,30382,30383,30385],{},"If you see ",[20,30384,13161],{},", check the Gunicorn service status, socket path, and socket permissions first.",[11,30387,22787],{"id":22786},[16,30389,30390],{},"For safer deploys, keep:",[63,30392,30393,30396,30399,30404,30407],{},[66,30394,30395],{},"previous known-good commit or release",[66,30397,30398],{},"previous dependency set or lockfile",[66,30400,30401,30402],{},"current ",[20,30403,191],{},[66,30405,30406],{},"database backup or migration rollback plan",[66,30408,30409],{},"backups for uploaded media if your app accepts files",[16,30411,30412],{},"A simple rollback if the new code is bad:",[1173,30414,30415,30422,30425,30427],{},[66,30416,30417,30418,30421],{},"redeploy the previous known-good commit into ",[20,30419,30420],{},"\u002Fopt\u002Fmyapp\u002Fcurrent"," or switch back if you use a release symlink pattern",[66,30423,30424],{},"reinstall prior dependencies if they changed",[66,30426,22673],{},[66,30428,30429,30430,30432],{},"verify the app with ",[20,30431,2764],{}," and logs",[16,30434,12414],{},[106,30436,30438],{"className":108,"code":30437,"language":110,"meta":111,"style":111},"cd \u002Fopt\u002Fmyapp\u002Fcurrent\ngit checkout \u003Cprevious-known-good-commit>\nsource \u002Fopt\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install -r requirements.txt\nsudo systemctl restart gunicorn-myapp\nsudo systemctl status gunicorn-myapp\ncurl -I https:\u002F\u002Fexample.com\n",[20,30439,30440,30446,30463,30469,30479,30489,30499],{"__ignoreMap":111},[115,30441,30442,30444],{"class":117,"line":118},[115,30443,5303],{"class":202},[115,30445,29572],{"class":132},[115,30447,30448,30450,30453,30455,30458,30461],{"class":117,"line":136},[115,30449,13525],{"class":262},[115,30451,30452],{"class":132}," checkout",[115,30454,7691],{"class":121},[115,30456,30457],{"class":132},"previous-known-good-commi",[115,30459,30460],{"class":125},"t",[115,30462,17380],{"class":121},[115,30464,30465,30467],{"class":117,"line":149},[115,30466,5311],{"class":202},[115,30468,29282],{"class":132},[115,30470,30471,30473,30475,30477],{"class":117,"line":162},[115,30472,8618],{"class":262},[115,30474,6600],{"class":132},[115,30476,12350],{"class":202},[115,30478,12353],{"class":132},[115,30480,30481,30483,30485,30487],{"class":117,"line":175},[115,30482,2001],{"class":262},[115,30484,3480],{"class":132},[115,30486,3483],{"class":132},[115,30488,15480],{"class":132},[115,30490,30491,30493,30495,30497],{"class":117,"line":350},[115,30492,2001],{"class":262},[115,30494,3480],{"class":132},[115,30496,1984],{"class":132},[115,30498,15480],{"class":132},[115,30500,30501,30503,30505],{"class":117,"line":365},[115,30502,2764],{"class":262},[115,30504,2767],{"class":202},[115,30506,2770],{"class":132},[16,30508,30509],{},"If a migration changed schema destructively, code rollback alone may not be enough. Treat those releases separately and plan backups before deployment.",[11,30511,1321],{"id":1320},[16,30513,30514,30515,30517],{},"This setup works because each component has a clear role. Gunicorn runs Django as a managed service under ",[20,30516,1277],{},", so it starts on boot and restarts on failure. Nginx handles client connections, static files, and TLS more efficiently than Gunicorn. Using a Unix socket keeps app traffic local to the host and avoids exposing Gunicorn directly.",[16,30519,30520],{},"Choose this approach when you want a stable Ubuntu deployment without containers. If you need multiple app servers, stronger release consistency across environments, or easier horizontal scaling, a container-based or load-balanced deployment may be a better fit.",[52,30522,11436],{"id":11435},[16,30524,30525,30526,30528],{},"This manual process is fine for a first production setup, but it becomes repetitive once you manage multiple apps or environments. The first pieces worth standardizing are the directory layout, ",[20,30527,1277],{}," unit, Nginx server block, environment file schema, and post-deploy smoke checks. A release-based deploy pattern is also a good candidate for automation because it makes rollback safer.",[11,30530,30532],{"id":30531},"edge-cases-or-notes","Edge cases or notes",[63,30534,30535,30541,30550,30559,30565],{},[66,30536,30537,30540],{},[1226,30538,30539],{},"PostgreSQL on another host:"," allow network access only from the app server, and use SSL if required by your database service.",[66,30542,30543,30546,30547,30549],{},[1226,30544,30545],{},"User uploads:"," Nginx can serve ",[20,30548,13085],{},", but you still need backups for uploaded files.",[66,30551,30552,30555,30556,30558],{},[1226,30553,30554],{},"Socket permissions:"," if Nginx cannot reach the socket, check the Gunicorn service ",[20,30557,2073],{},", runtime directory, and the socket path used in both configs.",[66,30560,30561,30564],{},[1226,30562,30563],{},"Long requests:"," if requests time out, review Gunicorn worker count and Nginx timeout settings instead of increasing them blindly.",[66,30566,30567,30570],{},[1226,30568,30569],{},"ASGI apps:"," for Django Channels or websockets, this Gunicorn WSGI setup is not enough; use an ASGI deployment path instead.",[11,30572,1386],{"id":1385},[63,30574,30575,30581,30587,30592],{},[66,30576,30577,30578],{},"Foundation: ",[1395,30579,30580],{"href":2978},"How Django Works in Production: Nginx, Gunicorn, Static Files, and the App Server",[66,30582,30583,30584],{},"Related implementation: ",[1395,30585,30586],{"href":2978},"Configure Django Static Files for Nginx in Production",[66,30588,30583,30589],{},[1395,30590,30591],{"href":3006},"Set Up Django Environment Variables on Ubuntu for Production",[66,30593,30594,30595],{},"Troubleshooting: ",[1395,30596,30597],{"href":4455},"Fix 502 Bad Gateway Between Nginx and Gunicorn for Django",[11,30599,1420],{"id":1419},[52,30601,30603],{"id":30602},"should-gunicorn-bind-to-a-unix-socket-or-a-localhost-tcp-port-on-ubuntu","Should Gunicorn bind to a Unix socket or a localhost TCP port on Ubuntu?",[16,30605,30606,30607,30609],{},"Use a Unix socket when Nginx and Gunicorn run on the same host. It is a common default and keeps Gunicorn local to the machine. Use ",[20,30608,20396],{}," if you prefer easier manual testing or have tooling that expects TCP.",[52,30611,30613],{"id":30612},"where-should-i-store-django-environment-variables-on-a-production-ubuntu-server","Where should I store Django environment variables on a production Ubuntu server?",[16,30615,30616,30617,4493,30620,30622],{},"Store them outside the Git repository in a restricted file such as ",[20,30618,30619],{},"\u002Fopt\u002Fmyapp\u002Fshared\u002F.env",[20,30621,14981],{},". Limit permissions so only the deploy user and required services can read them.",[52,30624,30626],{"id":30625},"why-does-nginx-return-502-bad-gateway-after-i-start-gunicorn","Why does Nginx return 502 Bad Gateway after I start Gunicorn?",[16,30628,30629,30630,1153,30632,20346,30635,211],{},"Usually one of these is wrong: Gunicorn is not running, the socket path does not match Nginx, Nginx lacks permission to access the socket, or the Django app failed to import because settings or dependencies are broken. Check ",[20,30631,23834],{},[20,30633,30634],{},"journalctl -u gunicorn-myapp",[20,30636,30637],{},"\u002Fvar\u002Flog\u002Fnginx\u002Ferror.log",[52,30639,30641],{"id":30640},"do-i-need-nginx-if-gunicorn-can-serve-http-by-itself","Do I need Nginx if Gunicorn can serve HTTP by itself?",[16,30643,30644],{},"For production, usually yes. Nginx handles TLS, static files, buffering, and connection management better. Gunicorn should focus on running the Django application.",[52,30646,30648],{"id":30647},"what-is-the-safest-rollback-option-if-a-deploy-breaks-after-migrations","What is the safest rollback option if a deploy breaks after migrations?",[16,30650,30651],{},"The safest option is a release-based deploy with the previous code still available, plus a tested backup and migration strategy. If migrations were destructive, restoring code alone may not recover the app.",[1485,30653,30654],{},"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 .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 .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}",{"title":111,"searchDepth":149,"depth":149,"links":30656},[30657,30658,30659,30660,30661,30662,30663,30664,30665,30666,30667,30668,30671,30672,30673],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":29013,"depth":136,"text":29014},{"id":29048,"depth":136,"text":29049},{"id":29224,"depth":136,"text":29225},{"id":21876,"depth":136,"text":14116},{"id":22171,"depth":136,"text":22172},{"id":30171,"depth":136,"text":30172},{"id":30262,"depth":136,"text":30263},{"id":22786,"depth":136,"text":22787},{"id":1320,"depth":136,"text":1321,"children":30669},[30670],{"id":11435,"depth":149,"text":11436},{"id":30531,"depth":136,"text":30532},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":30674},[30675,30676,30677,30678,30679],{"id":30602,"depth":149,"text":30603},{"id":30612,"depth":149,"text":30613},{"id":30625,"depth":149,"text":30626},{"id":30640,"depth":149,"text":30641},{"id":30647,"depth":149,"text":30648},"Moving a Django app from local development to production usually fails on the same points: process management, reverse proxy configuration, static files, secrets, TLS, and safe...",{},"\u002Fdeploy-django-gunicorn-nginx-ubuntu",[14027,23041,16243],{"title":2986,"description":30680},[1557,14954,2156,30686],"ubuntu","deploy-django-gunicorn-nginx-ubuntu",[1557,14954,2156,30686],"y7EsFxUtfSGrHSt8eHeOjrNPjpcZ7Xflbwb3NFUXfO0",{"id":30691,"title":4456,"body":30692,"category":4543,"description":32342,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":32343,"navigation":309,"path":32344,"priority":3351,"related":32345,"role":4552,"section":4553,"seo":32347,"stack":32348,"stem":32349,"tags":32350,"type":4558,"__hash__":32351},"articles\u002Fdebug-django-502-bad-gateway.md",{"type":8,"value":30693,"toc":32288},[30694,30696,30701,30704,30724,30727,30741,30748,30750,30753,30797,30800,30877,30879,30883,30886,30890,30893,30916,30918,30931,30934,30938,30941,30972,30975,31007,31010,31030,31033,31047,31051,31054,31081,31084,31088,31092,31094,31127,31130,31164,31168,31171,31198,31200,31222,31228,31232,31235,31251,31254,31299,31302,31321,31325,31329,31332,31335,31389,31402,31405,31424,31427,31460,31463,31467,31470,31473,31514,31517,31559,31569,31572,31594,31598,31601,31630,31633,31644,31647,31651,31654,31656,31674,31677,31711,31718,31721,31725,31728,31742,31745,31749,31752,31769,31772,31776,31780,31793,31796,31800,31815,31818,31831,31835,31841,31843,31857,31861,31865,31868,31893,31896,31900,31922,31926,31929,31932,31946,31950,31953,31956,31987,31990,32010,32013,32017,32021,32023,32044,32047,32051,32054,32077,32079,32096,32099,32101,32107,32110,32127,32130,32133,32135,32139,32158,32162,32165,32179,32183,32186,32195,32199,32202,32204,32210,32220,32226,32235,32237,32241,32247,32251,32261,32269,32272,32276,32279,32283,32286],[11,30695,14],{"id":13},[16,30697,2885,30698,30700],{},[1226,30699,13161],{}," in a Django deployment usually means your reverse proxy is reachable, but it cannot get a valid response from the upstream app server.",[16,30702,30703],{},"In a typical production stack, that means:",[63,30705,30706,30712,30718,30721],{},[66,30707,30708,30711],{},[1226,30709,30710],{},"Nginx or Caddy"," is accepting the request",[66,30713,30714,30715,30717],{},"but ",[1226,30716,1650],{}," is stopped, unreachable, misconfigured, or crashing",[66,30719,30720],{},"or the proxy is pointing to the wrong socket or port",[66,30722,30723],{},"or the upstream process starts but fails before serving requests",[16,30725,30726],{},"This guide focuses on real production setups:",[63,30728,30729,30732,30735,30738],{},[66,30730,30731],{},"Linux servers",[66,30733,30734],{},"Nginx or Caddy in front of Django",[66,30736,30737],{},"Gunicorn or Uvicorn as the app server",[66,30739,30740],{},"Docker and non-Docker deployments",[16,30742,30743,30744,30747],{},"If your Django app returns 502 after a deploy, after a restart, or only in production, the fastest fix is to identify ",[1226,30745,30746],{},"which layer failed"," before restarting everything.",[11,30749,30],{"id":29},[16,30751,30752],{},"For a fast Django 502 Bad Gateway fix, use this sequence:",[1173,30754,30755,30758,30761,30764,30767,30770,30791,30794],{},[66,30756,30757],{},"Confirm the reverse proxy is running.",[66,30759,30760],{},"Confirm Gunicorn or Uvicorn is running.",[66,30762,30763],{},"Test the upstream directly on its local port or unix socket.",[66,30765,30766],{},"Read proxy logs and app logs before restarting services.",[66,30768,30769],{},"Verify that the proxy target matches the app bind address.",[66,30771,30772,30773],{},"Check for startup failures:\n",[63,30774,30775,30778,30782,30785,30788],{},[66,30776,30777],{},"missing env vars",[66,30779,20107,30780],{},[20,30781,2713],{},[66,30783,30784],{},"import or dependency errors",[66,30786,30787],{},"failed migrations",[66,30789,30790],{},"bad settings module",[66,30792,30793],{},"Restart only the failing service.",[66,30795,30796],{},"If the 502 started immediately after a release, roll back to the last known good version only after checking schema compatibility.",[16,30798,30799],{},"Useful commands:",[106,30801,30803],{"className":108,"code":30802,"language":110,"meta":111,"style":111},"sudo systemctl status nginx\nsudo systemctl status gunicorn\nsudo journalctl -u gunicorn -n 100 --no-pager\nsudo journalctl -u nginx -n 100 --no-pager\ncurl -I http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\ncurl --unix-socket \u002Frun\u002Fgunicorn\u002Fgunicorn.sock http:\u002F\u002Flocalhost\u002Fhealth\u002F\n",[20,30804,30805,30815,30825,30841,30857,30865],{"__ignoreMap":111},[115,30806,30807,30809,30811,30813],{"class":117,"line":118},[115,30808,2001],{"class":262},[115,30810,3480],{"class":132},[115,30812,1984],{"class":132},[115,30814,1996],{"class":132},[115,30816,30817,30819,30821,30823],{"class":117,"line":136},[115,30818,2001],{"class":262},[115,30820,3480],{"class":132},[115,30822,1984],{"class":132},[115,30824,1987],{"class":132},[115,30826,30827,30829,30831,30833,30835,30837,30839],{"class":117,"line":149},[115,30828,2001],{"class":262},[115,30830,5030],{"class":132},[115,30832,2788],{"class":202},[115,30834,2791],{"class":132},[115,30836,2794],{"class":202},[115,30838,2797],{"class":202},[115,30840,2800],{"class":202},[115,30842,30843,30845,30847,30849,30851,30853,30855],{"class":117,"line":162},[115,30844,2001],{"class":262},[115,30846,5030],{"class":132},[115,30848,2788],{"class":202},[115,30850,3906],{"class":132},[115,30852,2794],{"class":202},[115,30854,2797],{"class":202},[115,30856,2800],{"class":202},[115,30858,30859,30861,30863],{"class":117,"line":175},[115,30860,2764],{"class":262},[115,30862,2767],{"class":202},[115,30864,28560],{"class":132},[115,30866,30867,30869,30871,30874],{"class":117,"line":350},[115,30868,2764],{"class":262},[115,30870,13366],{"class":202},[115,30872,30873],{"class":132}," \u002Frun\u002Fgunicorn\u002Fgunicorn.sock",[115,30875,30876],{"class":132}," http:\u002F\u002Flocalhost\u002Fhealth\u002F\n",[11,30878,43],{"id":42},[11,30880,30882],{"id":30881},"step-1-confirm-which-layer-is-failing","Step 1 — Confirm which layer is failing",[16,30884,30885],{},"Start by separating proxy problems from app server problems.",[52,30887,30889],{"id":30888},"check-whether-nginx-or-caddy-is-healthy","Check whether Nginx or Caddy is healthy",[16,30891,30892],{},"For Nginx:",[106,30894,30896],{"className":108,"code":30895,"language":110,"meta":111,"style":111},"sudo systemctl status nginx\nsudo nginx -t\n",[20,30897,30898,30908],{"__ignoreMap":111},[115,30899,30900,30902,30904,30906],{"class":117,"line":118},[115,30901,2001],{"class":262},[115,30903,3480],{"class":132},[115,30905,1984],{"class":132},[115,30907,1996],{"class":132},[115,30909,30910,30912,30914],{"class":117,"line":136},[115,30911,2001],{"class":262},[115,30913,3906],{"class":132},[115,30915,4282],{"class":202},[16,30917,17389],{},[63,30919,30920,30926],{},[66,30921,30922,30923],{},"service should be ",[20,30924,30925],{},"active (running)",[66,30927,30928,30930],{},[20,30929,7611],{}," should report syntax is OK",[16,30932,30933],{},"If config validation fails, do not reload Nginx yet. Fix syntax first.",[52,30935,30937],{"id":30936},"check-whether-gunicorn-or-uvicorn-is-running","Check whether Gunicorn or Uvicorn is running",[16,30939,30940],{},"For Gunicorn:",[106,30942,30944],{"className":108,"code":30943,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn\nsudo journalctl -u gunicorn -n 100 --no-pager\n",[20,30945,30946,30956],{"__ignoreMap":111},[115,30947,30948,30950,30952,30954],{"class":117,"line":118},[115,30949,2001],{"class":262},[115,30951,3480],{"class":132},[115,30953,1984],{"class":132},[115,30955,1987],{"class":132},[115,30957,30958,30960,30962,30964,30966,30968,30970],{"class":117,"line":136},[115,30959,2001],{"class":262},[115,30961,5030],{"class":132},[115,30963,2788],{"class":202},[115,30965,2791],{"class":132},[115,30967,2794],{"class":202},[115,30969,2797],{"class":202},[115,30971,2800],{"class":202},[16,30973,30974],{},"For Uvicorn:",[106,30976,30978],{"className":108,"code":30977,"language":110,"meta":111,"style":111},"sudo systemctl status uvicorn\nsudo journalctl -u uvicorn -n 100 --no-pager\n",[20,30979,30980,30990],{"__ignoreMap":111},[115,30981,30982,30984,30986,30988],{"class":117,"line":118},[115,30983,2001],{"class":262},[115,30985,3480],{"class":132},[115,30987,1984],{"class":132},[115,30989,12375],{"class":132},[115,30991,30992,30994,30996,30998,31001,31003,31005],{"class":117,"line":136},[115,30993,2001],{"class":262},[115,30995,5030],{"class":132},[115,30997,2788],{"class":202},[115,30999,31000],{"class":132}," uvicorn",[115,31002,2794],{"class":202},[115,31004,2797],{"class":202},[115,31006,2800],{"class":202},[16,31008,31009],{},"Also check listeners:",[106,31011,31013],{"className":108,"code":31012,"language":110,"meta":111,"style":111},"ss -ltnp\nls -l \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\n",[20,31014,31015,31021],{"__ignoreMap":111},[115,31016,31017,31019],{"class":117,"line":118},[115,31018,6472],{"class":262},[115,31020,15927],{"class":202},[115,31022,31023,31025,31027],{"class":117,"line":136},[115,31024,532],{"class":262},[115,31026,14881],{"class":202},[115,31028,31029],{"class":132}," \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\n",[16,31031,31032],{},"You are looking for one of these states:",[63,31034,31035,31038,31041,31044],{},[66,31036,31037],{},"app server is not running at all",[66,31039,31040],{},"app server is running but bound to the wrong port or socket",[66,31042,31043],{},"socket file does not exist",[66,31045,31046],{},"service is crash-looping",[52,31048,31050],{"id":31049},"distinguish-the-failure-type","Distinguish the failure type",[16,31052,31053],{},"A Django 502 usually falls into one of these groups:",[63,31055,31056,31061,31066,31071,31076],{},[66,31057,31058],{},[1226,31059,31060],{},"stopped upstream service",[66,31062,31063],{},[1226,31064,31065],{},"wrong upstream target",[66,31067,31068],{},[1226,31069,31070],{},"unix socket permission issue",[66,31072,31073],{},[1226,31074,31075],{},"app crash on boot",[66,31077,31078],{},[1226,31079,31080],{},"timeout or resource exhaustion",[16,31082,31083],{},"Do not restart both proxy and app blindly. That can hide the original error and lengthen the outage.",[11,31085,31087],{"id":31086},"step-2-read-the-right-logs-first","Step 2 — Read the right logs first",[52,31089,31091],{"id":31090},"reverse-proxy-logs","Reverse proxy logs",[16,31093,30892],{},[106,31095,31097],{"className":108,"code":31096,"language":110,"meta":111,"style":111},"sudo journalctl -u nginx -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,31098,31099,31115],{"__ignoreMap":111},[115,31100,31101,31103,31105,31107,31109,31111,31113],{"class":117,"line":118},[115,31102,2001],{"class":262},[115,31104,5030],{"class":132},[115,31106,2788],{"class":202},[115,31108,3906],{"class":132},[115,31110,2794],{"class":202},[115,31112,2797],{"class":202},[115,31114,2800],{"class":202},[115,31116,31117,31119,31121,31123,31125],{"class":117,"line":136},[115,31118,2001],{"class":262},[115,31120,13188],{"class":132},[115,31122,2794],{"class":202},[115,31124,2797],{"class":202},[115,31126,13195],{"class":132},[16,31128,31129],{},"Common patterns:",[63,31131,31132,31140,31148,31156],{},[66,31133,31134,31137,31139],{},[20,31135,31136],{},"connect() failed (2: No such file or directory)",[20160,31138],{},"\nNginx expects a unix socket that does not exist.",[66,31141,31142,31145,31147],{},[20,31143,31144],{},"connect() failed (13: Permission denied)",[20160,31146],{},"\nNginx cannot access the socket or parent directory.",[66,31149,31150,31153,31155],{},[20,31151,31152],{},"connect() failed (111: Connection refused)",[20160,31154],{},"\nnothing is listening on the target port.",[66,31157,31158,31161,31163],{},[20,31159,31160],{},"upstream prematurely closed connection",[20160,31162],{},"\napp accepted the connection but crashed or exited before completing the response.",[52,31165,31167],{"id":31166},"app-server-logs","App server logs",[16,31169,31170],{},"Typical Gunicorn or Uvicorn startup failures include:",[63,31172,31173,31177,31182,31186,31189,31192,31195],{},[66,31174,5071,31175],{},[20,31176,5074],{},[66,31178,20107,31179,31181],{},[20,31180,191],{}," or environment variable",[66,31183,20107,31184],{},[20,31185,2713],{},[66,31187,31188],{},"import error after code deploy",[66,31190,31191],{},"missing Python package in the virtualenv or image",[66,31193,31194],{},"database connection failure during startup",[66,31196,31197],{},"migration-related code path failing on boot",[16,31199,12414],{},[106,31201,31203],{"className":108,"code":31202,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 200 --no-pager\n",[20,31204,31205],{"__ignoreMap":111},[115,31206,31207,31209,31211,31213,31215,31217,31220],{"class":117,"line":118},[115,31208,2001],{"class":262},[115,31210,5030],{"class":132},[115,31212,2788],{"class":202},[115,31214,2791],{"class":132},[115,31216,2794],{"class":202},[115,31218,31219],{"class":202}," 200",[115,31221,2800],{"class":202},[16,31223,31224,31225,31227],{},"If the service exits immediately after ",[20,31226,2107],{},", focus on the app server unit or the release contents, not Nginx.",[52,31229,31231],{"id":31230},"systemd-journal-or-container-logs","systemd journal or container logs",[16,31233,31234],{},"For non-Docker:",[106,31236,31238],{"className":108,"code":31237,"language":110,"meta":111,"style":111},"sudo journalctl -xe --no-pager\n",[20,31239,31240],{"__ignoreMap":111},[115,31241,31242,31244,31246,31249],{"class":117,"line":118},[115,31243,2001],{"class":262},[115,31245,5030],{"class":132},[115,31247,31248],{"class":202}," -xe",[115,31250,2800],{"class":202},[16,31252,31253],{},"For Docker or Compose:",[106,31255,31257],{"className":108,"code":31256,"language":110,"meta":111,"style":111},"docker ps\ndocker logs \u003Ccontainer>\ndocker compose ps\ndocker compose logs web\n",[20,31258,31259,31265,31281,31289],{"__ignoreMap":111},[115,31260,31261,31263],{"class":117,"line":118},[115,31262,3295],{"class":262},[115,31264,4790],{"class":132},[115,31266,31267,31269,31271,31273,31276,31279],{"class":117,"line":136},[115,31268,3295],{"class":262},[115,31270,3301],{"class":132},[115,31272,7691],{"class":121},[115,31274,31275],{"class":132},"containe",[115,31277,31278],{"class":125},"r",[115,31280,17380],{"class":121},[115,31282,31283,31285,31287],{"class":117,"line":149},[115,31284,3295],{"class":262},[115,31286,3298],{"class":132},[115,31288,4790],{"class":132},[115,31290,31291,31293,31295,31297],{"class":117,"line":162},[115,31292,3295],{"class":262},[115,31294,3298],{"class":132},[115,31296,3301],{"class":132},[115,31298,3510],{"class":132},[16,31300,31301],{},"Look for:",[63,31303,31304,31307,31312,31315,31318],{},[66,31305,31306],{},"restart loops",[66,31308,31309,31311],{},[20,31310,2107],{}," failures",[66,31313,31314],{},"OOM kills",[66,31316,31317],{},"missing files inside the container",[66,31319,31320],{},"healthcheck failures",[11,31322,31324],{"id":31323},"step-3-fix-the-most-common-django-502-causes","Step 3 — Fix the most common Django 502 causes",[52,31326,31328],{"id":31327},"upstream-process-is-not-running","Upstream process is not running",[16,31330,31331],{},"If Gunicorn or Uvicorn is stopped, inspect the unit file and logs before restarting.",[16,31333,31334],{},"Example Gunicorn unit:",[106,31336,31338],{"className":2026,"code":31337,"language":2028,"meta":111,"style":111},"[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fapp\u002Fcurrent\nEnvironmentFile=\u002Fsrv\u002Fapp\u002Fshared\u002F.env\nRuntimeDirectory=gunicorn\nExecStart=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock project.wsgi:application\nRestart=on-failure\n",[20,31339,31340,31344,31350,31356,31363,31370,31376,31383],{"__ignoreMap":111},[115,31341,31342],{"class":117,"line":118},[115,31343,2060],{"class":262},[115,31345,31346,31348],{"class":117,"line":136},[115,31347,2065],{"class":121},[115,31349,2076],{"class":125},[115,31351,31352,31354],{"class":117,"line":149},[115,31353,2073],{"class":121},[115,31355,2076],{"class":125},[115,31357,31358,31360],{"class":117,"line":162},[115,31359,2081],{"class":121},[115,31361,31362],{"class":125},"=\u002Fsrv\u002Fapp\u002Fcurrent\n",[115,31364,31365,31367],{"class":117,"line":175},[115,31366,2089],{"class":121},[115,31368,31369],{"class":125},"=\u002Fsrv\u002Fapp\u002Fshared\u002F.env\n",[115,31371,31372,31374],{"class":117,"line":350},[115,31373,2098],{"class":121},[115,31375,2101],{"class":125},[115,31377,31378,31380],{"class":117,"line":365},[115,31379,2107],{"class":121},[115,31381,31382],{"class":125},"=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock project.wsgi:application\n",[115,31384,31385,31387],{"class":117,"line":380},[115,31386,2116],{"class":121},[115,31388,2119],{"class":125},[16,31390,31391,31394,31395,31398,31399,31401],{},[20,31392,31393],{},"RuntimeDirectory=gunicorn"," matters if you bind under ",[20,31396,31397],{},"\u002Frun",", because ",[20,31400,31397],{}," is temporary and recreated at boot.",[16,31403,31404],{},"Common problems:",[63,31406,31407,31410,31415,31419],{},[66,31408,31409],{},"wrong virtualenv path",[66,31411,31412,31413],{},"wrong ",[20,31414,2081],{},[66,31416,20107,31417],{},[20,31418,2089],{},[66,31420,31421,31422],{},"bad module path such as ",[20,31423,16986],{},[16,31425,31426],{},"After fixing the unit:",[106,31428,31430],{"className":108,"code":31429,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl restart gunicorn\nsudo systemctl status gunicorn\n",[20,31431,31432,31440,31450],{"__ignoreMap":111},[115,31433,31434,31436,31438],{"class":117,"line":118},[115,31435,2001],{"class":262},[115,31437,3480],{"class":132},[115,31439,4984],{"class":132},[115,31441,31442,31444,31446,31448],{"class":117,"line":136},[115,31443,2001],{"class":262},[115,31445,3480],{"class":132},[115,31447,3483],{"class":132},[115,31449,1987],{"class":132},[115,31451,31452,31454,31456,31458],{"class":117,"line":149},[115,31453,2001],{"class":262},[115,31455,3480],{"class":132},[115,31457,1984],{"class":132},[115,31459,1987],{"class":132},[16,31461,31462],{},"Rollback note: if this broke after a deployment, restore the previous release symlink or previous unit file before restarting repeatedly.",[52,31464,31466],{"id":31465},"wrong-socket-or-upstream-port-in-reverse-proxy-config","Wrong socket or upstream port in reverse proxy config",[16,31468,31469],{},"Your proxy and app bind target must match.",[16,31471,31472],{},"Nginx with TCP:",[106,31474,31476],{"className":2154,"code":31475,"language":2156,"meta":111,"style":111},"location \u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n",[20,31477,31478,31486,31492,31498,31504,31510],{"__ignoreMap":111},[115,31479,31480,31482,31484],{"class":117,"line":118},[115,31481,7128],{"class":121},[115,31483,2268],{"class":262},[115,31485,2220],{"class":125},[115,31487,31488,31490],{"class":117,"line":136},[115,31489,7137],{"class":121},[115,31491,3748],{"class":125},[115,31493,31494,31496],{"class":117,"line":149},[115,31495,7144],{"class":121},[115,31497,2288],{"class":125},[115,31499,31500,31502],{"class":117,"line":162},[115,31501,7144],{"class":121},[115,31503,2312],{"class":125},[115,31505,31506,31508],{"class":117,"line":175},[115,31507,7144],{"class":121},[115,31509,2304],{"class":125},[115,31511,31512],{"class":117,"line":350},[115,31513,2323],{"class":125},[16,31515,31516],{},"Nginx with unix socket:",[106,31518,31520],{"className":2154,"code":31519,"language":2156,"meta":111,"style":111},"location \u002F {\n    proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock:\u002F;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n",[20,31521,31522,31530,31537,31543,31549,31555],{"__ignoreMap":111},[115,31523,31524,31526,31528],{"class":117,"line":118},[115,31525,7128],{"class":121},[115,31527,2268],{"class":262},[115,31529,2220],{"class":125},[115,31531,31532,31534],{"class":117,"line":136},[115,31533,7137],{"class":121},[115,31535,31536],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock:\u002F;\n",[115,31538,31539,31541],{"class":117,"line":149},[115,31540,7144],{"class":121},[115,31542,2288],{"class":125},[115,31544,31545,31547],{"class":117,"line":162},[115,31546,7144],{"class":121},[115,31548,2312],{"class":125},[115,31550,31551,31553],{"class":117,"line":175},[115,31552,7144],{"class":121},[115,31554,2304],{"class":125},[115,31556,31557],{"class":117,"line":350},[115,31558,2323],{"class":125},[16,31560,31561,31562,31564,31565,31568],{},"If Gunicorn binds to ",[20,31563,20396],{},", Nginx cannot use ",[20,31566,31567],{},"\u002Frun\u002Fgunicorn\u002Fgunicorn.sock",", and the reverse is also true.",[16,31570,31571],{},"Validate and reload only after checking:",[106,31573,31574],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,31575,31576,31584],{"__ignoreMap":111},[115,31577,31578,31580,31582],{"class":117,"line":118},[115,31579,2001],{"class":262},[115,31581,3906],{"class":132},[115,31583,4282],{"class":202},[115,31585,31586,31588,31590,31592],{"class":117,"line":136},[115,31587,2001],{"class":262},[115,31589,3480],{"class":132},[115,31591,3919],{"class":132},[115,31593,1996],{"class":132},[52,31595,31597],{"id":31596},"unix-socket-permission-problems","Unix socket permission problems",[16,31599,31600],{},"Check the socket and every parent directory:",[106,31602,31604],{"className":108,"code":31603,"language":110,"meta":111,"style":111},"ls -l \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\nnamei -l \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\nstat \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\n",[20,31605,31606,31614,31623],{"__ignoreMap":111},[115,31607,31608,31610,31612],{"class":117,"line":118},[115,31609,532],{"class":262},[115,31611,14881],{"class":202},[115,31613,31029],{"class":132},[115,31615,31616,31619,31621],{"class":117,"line":136},[115,31617,31618],{"class":262},"namei",[115,31620,14881],{"class":202},[115,31622,31029],{"class":132},[115,31624,31625,31628],{"class":117,"line":149},[115,31626,31627],{"class":202},"stat",[115,31629,31029],{"class":132},[16,31631,31632],{},"Typical issue:",[63,31634,31635,31638],{},[66,31636,31637],{},"socket is owned by a user or group Nginx cannot access",[66,31639,31640,31643],{},[20,31641,31642],{},"\u002Frun\u002Fgunicorn"," permissions block traversal",[16,31645,31646],{},"Fix ownership narrowly. Do not make sockets world-writable.",[52,31648,31650],{"id":31649},"django-app-fails-on-startup","Django app fails on startup",[16,31652,31653],{},"A bad deploy often causes 502 symptoms right after release.",[16,31655,5438],{},[63,31657,31658,31662,31665,31668,31671],{},[66,31659,31660],{},[20,31661,5074],{},[66,31663,31664],{},"secret keys and env vars",[66,31666,31667],{},"dependency installation",[66,31669,31670],{},"migration state",[66,31672,31673],{},"release path correctness",[16,31675,31676],{},"Useful verification:",[106,31678,31680],{"className":108,"code":31679,"language":110,"meta":111,"style":111},"readlink -f \u002Fsrv\u002Fapp\u002Fcurrent\nsudo -u www-data \u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fpython \u002Fsrv\u002Fapp\u002Fcurrent\u002Fmanage.py check --deploy\n",[20,31681,31682,31692],{"__ignoreMap":111},[115,31683,31684,31687,31689],{"class":117,"line":118},[115,31685,31686],{"class":262},"readlink",[115,31688,2777],{"class":202},[115,31690,31691],{"class":132}," \u002Fsrv\u002Fapp\u002Fcurrent\n",[115,31693,31694,31696,31698,31701,31704,31707,31709],{"class":117,"line":136},[115,31695,2001],{"class":262},[115,31697,2788],{"class":202},[115,31699,31700],{"class":132}," www-data",[115,31702,31703],{"class":132}," \u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fpython",[115,31705,31706],{"class":132}," \u002Fsrv\u002Fapp\u002Fcurrent\u002Fmanage.py",[115,31708,1814],{"class":132},[115,31710,1817],{"class":202},[16,31712,31713,31714,31717],{},"If your app touches the database or Redis during import time, ",[20,31715,31716],{},"AppConfig"," startup, or early request initialization, verify those dependencies too.",[16,31719,31720],{},"If Django is behind a proxy and you enforce HTTPS redirects, make sure your proxy headers and Django settings are aligned. This is more likely to cause redirect or CSRF problems than a 502, but it is still worth verifying during proxy-related deploy changes.",[52,31722,31724],{"id":31723},"timeout-or-resource-exhaustion","Timeout or resource exhaustion",[16,31726,31727],{},"If logs show workers dying or requests hanging:",[63,31729,31730,31733,31736,31739],{},[66,31731,31732],{},"startup may be too slow after deploy",[66,31734,31735],{},"worker count may be too low",[66,31737,31738],{},"memory pressure may be killing workers",[66,31740,31741],{},"DB or Redis may be blocking startup",[16,31743,31744],{},"Check for OOM or worker exits in the journal. If this is resource-related, increasing workers alone may not help if memory is already exhausted.",[52,31746,31748],{"id":31747},"bad-release-or-incomplete-deploy","Bad release or incomplete deploy",[16,31750,31751],{},"Common release issues:",[63,31753,31754,31757,31760,31763,31766],{},[66,31755,31756],{},"code updated but virtualenv not updated",[66,31758,31759],{},"new image not actually running",[66,31761,31762],{},"symlink points to partial release",[66,31764,31765],{},"migrations ran in the wrong order",[66,31767,31768],{},"stale socket left behind from an old process",[16,31770,31771],{},"If the 502 began immediately after release, rolling back is usually safer than trying to patch production live.",[11,31773,31775],{"id":31774},"step-4-verify-upstream-connectivity-directly","Step 4 — Verify upstream connectivity directly",[52,31777,31779],{"id":31778},"test-a-tcp-upstream-locally","Test a TCP upstream locally",[106,31781,31783],{"className":108,"code":31782,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\n",[20,31784,31785],{"__ignoreMap":111},[115,31786,31787,31789,31791],{"class":117,"line":118},[115,31788,2764],{"class":262},[115,31790,2767],{"class":202},[115,31792,28560],{"class":132},[16,31794,31795],{},"If this fails locally, Nginx is not the primary problem.",[52,31797,31799],{"id":31798},"test-a-unix-socket-backed-upstream","Test a unix socket-backed upstream",[106,31801,31803],{"className":108,"code":31802,"language":110,"meta":111,"style":111},"curl --unix-socket \u002Frun\u002Fgunicorn\u002Fgunicorn.sock http:\u002F\u002Flocalhost\u002Fhealth\u002F\n",[20,31804,31805],{"__ignoreMap":111},[115,31806,31807,31809,31811,31813],{"class":117,"line":118},[115,31808,2764],{"class":262},[115,31810,13366],{"class":202},[115,31812,30873],{"class":132},[115,31814,30876],{"class":132},[16,31816,31817],{},"Also confirm the socket exists:",[106,31819,31821],{"className":108,"code":31820,"language":110,"meta":111,"style":111},"ls -l \u002Frun\u002Fgunicorn\u002Fgunicorn.sock\n",[20,31822,31823],{"__ignoreMap":111},[115,31824,31825,31827,31829],{"class":117,"line":118},[115,31826,532],{"class":262},[115,31828,14881],{"class":202},[115,31830,31029],{"class":132},[52,31832,31834],{"id":31833},"check-a-cheap-health-endpoint","Check a cheap health endpoint",[16,31836,31837,31838,31840],{},"Use a lightweight URL such as ",[20,31839,13411],{}," that avoids expensive queries. A successful local response confirms the app server can serve traffic before involving the proxy.",[16,31842,17389],{},[63,31844,31845,31851,31854],{},[66,31846,31847,31848,31850],{},"local curl returns ",[20,31849,17741],{}," or expected status",[66,31852,31853],{},"headers return quickly",[66,31855,31856],{},"app does not crash during the request",[11,31858,31860],{"id":31859},"step-5-restart-safely-and-avoid-making-the-outage-worse","Step 5 — Restart safely and avoid making the outage worse",[52,31862,31864],{"id":31863},"restart-only-the-failing-unit-first","Restart only the failing unit first",[16,31866,31867],{},"If Gunicorn failed, restart Gunicorn first:",[106,31869,31871],{"className":108,"code":31870,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn\nsudo systemctl status gunicorn\n",[20,31872,31873,31883],{"__ignoreMap":111},[115,31874,31875,31877,31879,31881],{"class":117,"line":118},[115,31876,2001],{"class":262},[115,31878,3480],{"class":132},[115,31880,3483],{"class":132},[115,31882,1987],{"class":132},[115,31884,31885,31887,31889,31891],{"class":117,"line":136},[115,31886,2001],{"class":262},[115,31888,3480],{"class":132},[115,31890,1984],{"class":132},[115,31892,1987],{"class":132},[16,31894,31895],{},"Do not restart Nginx unless Nginx config changed or Nginx itself failed.",[52,31897,31899],{"id":31898},"reload-reverse-proxy-only-after-config-test-passes","Reload reverse proxy only after config test passes",[106,31901,31902],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,31903,31904,31912],{"__ignoreMap":111},[115,31905,31906,31908,31910],{"class":117,"line":118},[115,31907,2001],{"class":262},[115,31909,3906],{"class":132},[115,31911,4282],{"class":202},[115,31913,31914,31916,31918,31920],{"class":117,"line":136},[115,31915,2001],{"class":262},[115,31917,3480],{"class":132},[115,31919,3919],{"class":132},[115,31921,1996],{"class":132},[52,31923,31925],{"id":31924},"confirm-migrations-and-static-files-before-retrying-full-traffic","Confirm migrations and static files before retrying full traffic",[16,31927,31928],{},"A restarted app that still depends on missing schema or static assets can fail again.",[16,31930,31931],{},"Check your release process:",[63,31933,31934,31937,31943],{},[66,31935,31936],{},"were migrations applied?",[66,31938,31939,31940,31942],{},"was ",[20,31941,13689],{}," run if needed?",[66,31944,31945],{},"does the current release contain the expected code?",[52,31947,31949],{"id":31948},"roll-back-if-the-502-started-immediately-after-a-release","Roll back if the 502 started immediately after a release",[16,31951,31952],{},"A safe rollback path is critical.",[16,31954,31955],{},"Example checks:",[106,31957,31959],{"className":108,"code":31958,"language":110,"meta":111,"style":111},"readlink -f \u002Fsrv\u002Fapp\u002Fcurrent\nsudo systemctl restart gunicorn\ncurl -I http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\n",[20,31960,31961,31969,31979],{"__ignoreMap":111},[115,31962,31963,31965,31967],{"class":117,"line":118},[115,31964,31686],{"class":262},[115,31966,2777],{"class":202},[115,31968,31691],{"class":132},[115,31970,31971,31973,31975,31977],{"class":117,"line":136},[115,31972,2001],{"class":262},[115,31974,3480],{"class":132},[115,31976,3483],{"class":132},[115,31978,1987],{"class":132},[115,31980,31981,31983,31985],{"class":117,"line":149},[115,31982,2764],{"class":262},[115,31984,2767],{"class":202},[115,31986,28560],{"class":132},[16,31988,31989],{},"Rollback sequence:",[1173,31991,31992,31995,31998,32001,32004,32007],{},[66,31993,31994],{},"verify whether the failed release included schema changes",[66,31996,31997],{},"repoint to the previous release only if the previous code is still compatible with the current database schema",[66,31999,32000],{},"restart the app service",[66,32002,32003],{},"verify local upstream health",[66,32005,32006],{},"test through Nginx",[66,32008,32009],{},"only then resume normal traffic",[16,32011,32012],{},"Do not roll back application code blindly if the failed release included non-backward-compatible migrations; verify schema compatibility first or restore both code and database from a known-good recovery point.",[11,32014,32016],{"id":32015},"step-6-deployment-specific-fixes","Step 6 — Deployment-specific fixes",[52,32018,32020],{"id":32019},"non-docker-django-deployments","Non-Docker Django deployments",[16,32022,20088],{},[63,32024,32025,32030,32035,32041],{},[66,32026,32027,32028],{},"virtualenv path mismatch in ",[20,32029,2107],{},[66,32031,32032,32033],{},"incorrect ",[20,32034,2081],{},[66,32036,32037,32038],{},"stale symlink target in ",[20,32039,32040],{},"\u002Fsrv\u002Fapp\u002Fcurrent",[66,32042,32043],{},"env file missing on the server",[16,32045,32046],{},"These problems often appear after manual deploy changes.",[52,32048,32050],{"id":32049},"docker-and-compose-deployments","Docker and Compose deployments",[16,32052,32053],{},"Check container state first:",[106,32055,32057],{"className":108,"code":32056,"language":110,"meta":111,"style":111},"docker compose ps\ndocker compose logs web\n",[20,32058,32059,32067],{"__ignoreMap":111},[115,32060,32061,32063,32065],{"class":117,"line":118},[115,32062,3295],{"class":262},[115,32064,3298],{"class":132},[115,32066,4790],{"class":132},[115,32068,32069,32071,32073,32075],{"class":117,"line":136},[115,32070,3295],{"class":262},[115,32072,3298],{"class":132},[115,32074,3301],{"class":132},[115,32076,3510],{"class":132},[16,32078,20088],{},[63,32080,32081,32084,32087,32090,32093],{},[66,32082,32083],{},"container exits immediately",[66,32085,32086],{},"service name in proxy config no longer matches",[66,32088,32089],{},"app binds to the wrong port",[66,32091,32092],{},"socket path mounted in the wrong location",[66,32094,32095],{},"healthcheck keeps the service out of rotation",[16,32097,32098],{},"If your proxy runs on the host and app runs in Docker, verify the host can actually reach the container port or mounted socket.",[11,32100,1321],{"id":1320},[16,32102,32103,32104,211],{},"A 502 is not a Django exception by itself. It is usually a ",[1226,32105,32106],{},"proxy-to-upstream failure",[16,32108,32109],{},"That is why the fastest fix workflow is:",[1173,32111,32112,32115,32118,32121,32124],{},[66,32113,32114],{},"identify the failing layer",[66,32116,32117],{},"verify the upstream directly",[66,32119,32120],{},"inspect logs before restart",[66,32122,32123],{},"repair the bind target, process, or startup issue",[66,32125,32126],{},"only then reload the proxy",[16,32128,32129],{},"Unix sockets and localhost TCP both work in production. Use the one you can operate reliably. Unix sockets are common on single-host deployments. Localhost TCP is often simpler to debug.",[16,32131,32132],{},"When this process becomes repetitive across multiple apps, it is a good candidate for a reusable script or template. Common items to automate first are config validation, local upstream health checks, service status collection, and rollback when the health check fails.",[11,32134,11443],{"id":11442},[52,32136,32138],{"id":32137},"_502-vs-504-vs-500-in-django-deployments","502 vs 504 vs 500 in Django deployments",[63,32140,32141,32146,32152],{},[66,32142,32143,32145],{},[1226,32144,13800],{},": proxy could not get a valid upstream response",[66,32147,32148,32151],{},[1226,32149,32150],{},"504",": upstream took too long",[66,32153,32154,32157],{},[1226,32155,32156],{},"500",": Django returned an application error successfully through the proxy",[52,32159,32161],{"id":32160},"_502-only-on-admin-or-large-requests","502 only on admin or large requests",[16,32163,32164],{},"This can indicate:",[63,32166,32167,32170,32173,32176],{},[66,32168,32169],{},"app worker crashes under heavier queries",[66,32171,32172],{},"upstream timeout behavior",[66,32174,32175],{},"memory pressure",[66,32177,32178],{},"file upload or body size configuration mismatch",[52,32180,32182],{"id":32181},"_502-after-reboot-but-not-after-manual-restart","502 after reboot but not after manual restart",[16,32184,32185],{},"This often points to startup ordering, missing runtime directories, or a systemd unit that depends on paths or env files not present at boot.",[16,32187,32188,32189,32191,32192,32194],{},"If you bind Gunicorn to a unix socket under ",[20,32190,31397],{},", make sure the runtime directory is recreated at boot with ",[20,32193,13821],{}," or an equivalent socket-based setup.",[52,32196,32198],{"id":32197},"_502-during-zero-downtime-deploys","502 during zero-downtime deploys",[16,32200,32201],{},"Check that the new instance becomes healthy before traffic shifts. A rolling deploy without a real app health check can put broken instances behind the proxy.",[11,32203,1386],{"id":1385},[16,32205,32206,32207,211],{},"For background, see ",[1395,32208,32209],{"href":22931},"how Django requests flow through Nginx, Gunicorn, and the app server",[16,32211,32212,32213,3146,32216,211],{},"If you need a known-good baseline, use ",[1395,32214,32215],{"href":2985},"deploy Django with Gunicorn and Nginx",[1395,32217,32219],{"href":32218},"\u002Fdeploy\u002Fsetup-systemd-for-gunicorn-django","configure systemd for Django Gunicorn services",[16,32221,32222,32223,211],{},"If this outage started after a release, follow ",[1395,32224,32225],{"href":1415},"how to roll back a Django deployment safely",[16,32227,32228,32229,1153,32231,20346,32233,211],{},"For related production configuration issues that are not usually 502s, see ",[1395,32230,4446],{"href":4445},[1395,32232,6215],{"href":4551},[1395,32234,3108],{"href":4418},[11,32236,1420],{"id":1419},[52,32238,32240],{"id":32239},"why-does-django-return-502-bad-gateway-after-a-deploy","Why does Django return 502 Bad Gateway after a deploy?",[16,32242,32243,32244,32246],{},"Usually because the new release did not start correctly. Common causes are missing env vars, missing ",[20,32245,2713],{},", dependency mismatches, failed imports, wrong socket paths, or migrations that were skipped.",[52,32248,32250],{"id":32249},"how-do-i-tell-whether-nginx-or-gunicorn-is-causing-the-502","How do I tell whether Nginx or Gunicorn is causing the 502?",[16,32252,32253,32254,4493,32257,32260],{},"Test the upstream directly. If ",[20,32255,32256],{},"curl http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F",[20,32258,32259],{},"curl --unix-socket \u002Frun\u002Fgunicorn\u002Fgunicorn.sock http:\u002F\u002Flocalhost\u002Fhealth\u002F"," fails, the app side is the problem. If local upstream works, inspect Nginx config and logs.",[52,32262,32264,32265,32268],{"id":32263},"what-does-connect-to-unixrungunicorngunicornsock-failed-mean","What does ",[20,32266,32267],{},"connect() to unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock failed"," mean?",[16,32270,32271],{},"Nginx tried to connect to a unix socket and could not. The socket may be missing, the app may not be running, the socket path may be wrong, or permissions may block access.",[52,32273,32275],{"id":32274},"should-i-use-a-unix-socket-or-localhost-port-for-django-in-production","Should I use a unix socket or localhost port for Django in production?",[16,32277,32278],{},"Either is valid. Unix sockets are common for single-server setups. Localhost TCP is often easier to test and debug. Choose the option your team can verify and maintain consistently.",[52,32280,32282],{"id":32281},"why-does-restarting-nginx-not-fix-a-django-502-error","Why does restarting Nginx not fix a Django 502 error?",[16,32284,32285],{},"Because Nginx is often not the failing component. If Gunicorn or Uvicorn is crashed, misconfigured, or unreachable, restarting the proxy alone will not restore the upstream.",[1485,32287,30654],{},{"title":111,"searchDepth":149,"depth":149,"links":32289},[32290,32291,32292,32293,32298,32303,32311,32316,32322,32326,32327,32333,32334],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":30881,"depth":136,"text":30882,"children":32294},[32295,32296,32297],{"id":30888,"depth":149,"text":30889},{"id":30936,"depth":149,"text":30937},{"id":31049,"depth":149,"text":31050},{"id":31086,"depth":136,"text":31087,"children":32299},[32300,32301,32302],{"id":31090,"depth":149,"text":31091},{"id":31166,"depth":149,"text":31167},{"id":31230,"depth":149,"text":31231},{"id":31323,"depth":136,"text":31324,"children":32304},[32305,32306,32307,32308,32309,32310],{"id":31327,"depth":149,"text":31328},{"id":31465,"depth":149,"text":31466},{"id":31596,"depth":149,"text":31597},{"id":31649,"depth":149,"text":31650},{"id":31723,"depth":149,"text":31724},{"id":31747,"depth":149,"text":31748},{"id":31774,"depth":136,"text":31775,"children":32312},[32313,32314,32315],{"id":31778,"depth":149,"text":31779},{"id":31798,"depth":149,"text":31799},{"id":31833,"depth":149,"text":31834},{"id":31859,"depth":136,"text":31860,"children":32317},[32318,32319,32320,32321],{"id":31863,"depth":149,"text":31864},{"id":31898,"depth":149,"text":31899},{"id":31924,"depth":149,"text":31925},{"id":31948,"depth":149,"text":31949},{"id":32015,"depth":136,"text":32016,"children":32323},[32324,32325],{"id":32019,"depth":149,"text":32020},{"id":32049,"depth":149,"text":32050},{"id":1320,"depth":136,"text":1321},{"id":11442,"depth":136,"text":11443,"children":32328},[32329,32330,32331,32332],{"id":32137,"depth":149,"text":32138},{"id":32160,"depth":149,"text":32161},{"id":32181,"depth":149,"text":32182},{"id":32197,"depth":149,"text":32198},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":32335},[32336,32337,32338,32340,32341],{"id":32239,"depth":149,"text":32240},{"id":32249,"depth":149,"text":32250},{"id":32263,"depth":149,"text":32339},"What does connect() to unix:\u002Frun\u002Fgunicorn\u002Fgunicorn.sock failed mean?",{"id":32274,"depth":149,"text":32275},{"id":32281,"depth":149,"text":32282},"A 502 Bad Gateway in a Django deployment usually means your reverse proxy is reachable, but it cannot get a valid response from the upstream app server.",{},"\u002Fdebug-django-502-bad-gateway",[6332,32346,4551],"\u002Ffix-issues\u002Ffix-nginx-413-django-uploads",{"title":4456,"description":32342},[1557,2156,14954],"debug-django-502-bad-gateway",[1557,2156,14954],"k0Rs18GjW_uXOQrmR6gMjw2Tas-e33dlY3FdQg147S8",{"id":32353,"title":3000,"body":32354,"category":34157,"description":34158,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":34159,"navigation":309,"path":34160,"priority":3351,"related":34161,"role":34162,"section":34163,"seo":34164,"stack":34165,"stem":34166,"tags":34167,"type":3104,"__hash__":34168},"articles\u002Fdjango-deployment-checklist.md",{"type":8,"value":32355,"toc":34107},[32356,32358,32361,32367,32371,32374,32397,32401,32404,32418,32420,32422,32425,32466,32470,32473,32490,32492,32495,32499,32502,32505,32525,32528,32587,32589,32615,32618,32622,32625,32628,32646,32649,32672,32675,32679,32682,32702,32704,32726,32729,32741,32744,32748,32751,32754,32769,32771,32784,32787,32798,32801,32805,32808,32811,32828,32831,32872,32877,32923,32925,32943,32947,32950,32953,32991,32994,33177,33180,33209,33217,33220,33233,33239,33243,33246,33315,33321,33324,33345,33348,33359,33362,33376,33380,33383,33416,33418,33432,33434,33477,33481,33484,33507,33510,33559,33562,33651,33656,33659,33663,33666,33683,33685,33718,33721,33725,33728,33730,33744,33747,33751,33754,33768,33771,33775,33778,33800,33802,33821,33823,33827,33831,33870,33874,33912,33916,33919,33941,33945,33957,33959,33961,33965,33968,33972,33975,33979,33982,33984,33986,33990,33995,34001,34005,34008,34012,34015,34019,34022,34024,34026,34031,34036,34042,34050,34056,34058,34060,34064,34076,34080,34083,34087,34090,34094,34097,34101,34104],[11,32357,14],{"id":13},[16,32359,32360],{},"A Django app that works locally is not automatically safe to run in production. Most deployment failures come from a small set of issues: wrong settings, missing secrets, unsafe TLS or proxy handling, unreviewed migrations, broken static files, or no rollback plan.",[16,32362,13495,32363,32366],{},[1226,32364,32365],{},"Django deployment checklist for production"," is a practical pre-release and post-release runbook. It helps you verify that your app is ready to go live on a Linux server, VM, or container-based stack using common components like Gunicorn, Uvicorn, Nginx, Caddy, PostgreSQL, and Redis.",[52,32368,32370],{"id":32369},"what-this-django-deployment-checklist-covers","What this Django deployment checklist covers",[16,32372,32373],{},"This checklist focuses on production readiness:",[63,32375,32376,32379,32382,32385,32388,32391,32394],{},[66,32377,32378],{},"Django settings and environment separation",[66,32380,32381],{},"secrets and credentials",[66,32383,32384],{},"database readiness and backups",[66,32386,32387],{},"static and media file handling",[66,32389,32390],{},"app server, reverse proxy, and TLS checks",[66,32392,32393],{},"logging, monitoring, and background workers",[66,32395,32396],{},"deployment order, rollback, and post-deploy smoke tests",[52,32398,32400],{"id":32399},"what-this-checklist-does-not-replace","What this checklist does not replace",[16,32402,32403],{},"It does not replace:",[63,32405,32406,32409,32412,32415],{},[66,32407,32408],{},"a full infrastructure design",[66,32410,32411],{},"app-specific load testing",[66,32413,32414],{},"a disaster recovery plan for your entire platform",[66,32416,32417],{},"platform-specific docs from your host or managed database provider",[23099,32419],{},[11,32421,30],{"id":29},[16,32423,32424],{},"Before go-live, verify these minimum items:",[1173,32426,32427,32432,32436,32441,32444,32449,32452,32457,32460,32463],{},[66,32428,32429],{},[20,32430,32431],{},"DEBUG = False",[66,32433,32434,4152],{},[20,32435,2719],{},[66,32437,32438,32440],{},[20,32439,2713],{}," and database credentials come from environment variables or a secret manager",[66,32442,32443],{},"PostgreSQL is reachable and backups are enabled",[66,32445,32446,32448],{},[20,32447,15970],{}," passes",[66,32450,32451],{},"migrations are reviewed before applying",[66,32453,32454,32456],{},[20,32455,13689],{}," succeeds",[66,32458,32459],{},"Gunicorn or Uvicorn starts cleanly",[66,32461,32462],{},"Nginx or Caddy serves the app over HTTPS",[66,32464,32465],{},"logs, health checks, and rollback steps are ready",[52,32467,32469],{"id":32468},"the-highest-risk-items-to-verify-first","The highest-risk items to verify first",[16,32471,32472],{},"If time is limited, check these first:",[63,32474,32475,32478,32481,32484,32487],{},[66,32476,32477],{},"secrets are not hardcoded or committed",[66,32479,32480],{},"database migrations are safe for the current release",[66,32482,32483],{},"TLS and proxy headers are correct",[66,32485,32486],{},"static files and media storage are configured correctly",[66,32488,32489],{},"rollback is possible if the release fails",[23099,32491],{},[11,32493,32365],{"id":32494},"django-deployment-checklist-for-production",[52,32496,32498],{"id":32497},"_1-confirm-environment-separation","1. Confirm environment separation",[16,32500,32501],{},"Production settings must be separate from local development behavior.",[16,32503,32504],{},"Minimum checks:",[63,32506,32507,32511,32516,32519,32522],{},[66,32508,32509],{},[20,32510,32431],{},[66,32512,32513,32515],{},[20,32514,2719],{}," includes the real domain names",[66,32517,32518],{},"production settings are not mixed with dev defaults",[66,32520,32521],{},"secrets are not stored in Git",[66,32523,32524],{},"required environment variables must exist at startup",[16,32526,32527],{},"Example settings:",[106,32529,32531],{"className":2369,"code":32530,"language":1114,"meta":111,"style":111},"import os\n\nDEBUG = False\n\nALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\n",[20,32532,32533,32539,32543,32551,32555,32571,32575],{"__ignoreMap":111},[115,32534,32535,32537],{"class":117,"line":118},[115,32536,5613],{"class":121},[115,32538,5616],{"class":125},[115,32540,32541],{"class":117,"line":136},[115,32542,310],{"emptyLinePlaceholder":309},[115,32544,32545,32547,32549],{"class":117,"line":149},[115,32546,7350],{"class":202},[115,32548,2380],{"class":121},[115,32550,7355],{"class":202},[115,32552,32553],{"class":117,"line":162},[115,32554,310],{"emptyLinePlaceholder":309},[115,32556,32557,32559,32561,32563,32565,32567,32569],{"class":117,"line":175},[115,32558,2719],{"class":202},[115,32560,2380],{"class":121},[115,32562,7493],{"class":125},[115,32564,7496],{"class":132},[115,32566,1153],{"class":125},[115,32568,7501],{"class":132},[115,32570,2552],{"class":125},[115,32572,32573],{"class":117,"line":350},[115,32574,310],{"emptyLinePlaceholder":309},[115,32576,32577,32579,32581,32583,32585],{"class":117,"line":365},[115,32578,2713],{"class":202},[115,32580,2380],{"class":121},[115,32582,8861],{"class":125},[115,32584,12063],{"class":132},[115,32586,2552],{"class":125},[16,32588,8572],{},[106,32590,32592],{"className":108,"code":32591,"language":110,"meta":111,"style":111},"env | grep DJANGO\npython manage.py check --deploy\n",[20,32593,32594,32605],{"__ignoreMap":111},[115,32595,32596,32598,32600,32602],{"class":117,"line":118},[115,32597,2331],{"class":262},[115,32599,579],{"class":121},[115,32601,4838],{"class":262},[115,32603,32604],{"class":132}," DJANGO\n",[115,32606,32607,32609,32611,32613],{"class":117,"line":136},[115,32608,1114],{"class":262},[115,32610,1117],{"class":132},[115,32612,1814],{"class":132},[115,32614,1817],{"class":202},[16,32616,32617],{},"Rollback note: if the app fails at startup after a settings change, revert the settings file or restore the previous environment file before restarting the process.",[52,32619,32621],{"id":32620},"_2-verify-secret-and-credential-handling","2. Verify secret and credential handling",[16,32623,32624],{},"Do not hardcode production credentials in Django settings, Compose files committed to Git, or service files.",[16,32626,32627],{},"Check that these come from environment variables or a secret manager:",[63,32629,32630,32634,32637,32640,32643],{},[66,32631,32632],{},[20,32633,2713],{},[66,32635,32636],{},"database username and password",[66,32638,32639],{},"email credentials",[66,32641,32642],{},"third-party API keys",[66,32644,32645],{},"Redis URLs if used",[16,32647,32648],{},"Environment file structure example:",[106,32650,32652],{"className":2329,"code":32651,"language":2331,"meta":111,"style":111},"DJANGO_SECRET_KEY=replace-with-a-real-secret\nDATABASE_URL=postgresql:\u002F\u002Fappuser:strongpassword@127.0.0.1:5432\u002Fappdb\nREDIS_URL=redis:\u002F\u002F127.0.0.1:6379\u002F0\nDJANGO_ALLOWED_HOSTS=example.com,www.example.com\n",[20,32653,32654,32659,32664,32668],{"__ignoreMap":111},[115,32655,32656],{"class":117,"line":118},[115,32657,32658],{},"DJANGO_SECRET_KEY=replace-with-a-real-secret\n",[115,32660,32661],{"class":117,"line":136},[115,32662,32663],{},"DATABASE_URL=postgresql:\u002F\u002Fappuser:strongpassword@127.0.0.1:5432\u002Fappdb\n",[115,32665,32666],{"class":117,"line":149},[115,32667,15035],{},[115,32669,32670],{"class":117,"line":162},[115,32671,15020],{},[16,32673,32674],{},"Also confirm a basic rotation process exists. Even if it is manual, document where secrets live and how they are updated.",[52,32676,32678],{"id":32677},"_3-validate-database-readiness","3. Validate database readiness",[16,32680,32681],{},"Before deployment:",[63,32683,32684,32687,32690,32693,32696,32699],{},[66,32685,32686],{},"production PostgreSQL database exists",[66,32688,32689],{},"connectivity works",[66,32691,32692],{},"the application user has the right permissions",[66,32694,32695],{},"backups are enabled",[66,32697,32698],{},"restore has been tested at least once",[66,32700,32701],{},"migration impact has been reviewed",[16,32703,30799],{},[106,32705,32707],{"className":108,"code":32706,"language":110,"meta":111,"style":111},"python manage.py dbshell\npython manage.py showmigrations\n",[20,32708,32709,32718],{"__ignoreMap":111},[115,32710,32711,32713,32715],{"class":117,"line":118},[115,32712,1114],{"class":262},[115,32714,1117],{"class":132},[115,32716,32717],{"class":132}," dbshell\n",[115,32719,32720,32722,32724],{"class":117,"line":136},[115,32721,1114],{"class":262},[115,32723,1117],{"class":132},[115,32725,1129],{"class":132},[16,32727,32728],{},"Apply migrations only when you understand what they will do:",[106,32730,32731],{"className":108,"code":11313,"language":110,"meta":111,"style":111},[20,32732,32733],{"__ignoreMap":111},[115,32734,32735,32737,32739],{"class":117,"line":118},[115,32736,1114],{"class":262},[115,32738,1117],{"class":132},[115,32740,11324],{"class":132},[16,32742,32743],{},"Rollback note: database rollbacks are often harder than code rollbacks. If a migration is destructive, locks large tables, or is not backward-compatible with older code, plan that release separately.",[52,32745,32747],{"id":32746},"_4-review-static-and-media-file-handling","4. Review static and media file handling",[16,32749,32750],{},"Static files and user uploads are different problems.",[16,32752,32753],{},"Check static files:",[106,32755,32757],{"className":108,"code":32756,"language":110,"meta":111,"style":111},"python manage.py collectstatic --noinput\n",[20,32758,32759],{"__ignoreMap":111},[115,32760,32761,32763,32765,32767],{"class":117,"line":118},[115,32762,1114],{"class":262},[115,32764,1117],{"class":132},[115,32766,1838],{"class":132},[115,32768,1841],{"class":202},[16,32770,26735],{},[63,32772,32773,32776,32779],{},[66,32774,32775],{},"static files are served by Nginx, Caddy, or object storage",[66,32777,32778],{},"hashed filenames or cache-safe asset versioning is in place where relevant",[66,32780,32781,32782],{},"browser requests for CSS and JS return ",[20,32783,17741],{},[16,32785,32786],{},"For media:",[63,32788,32789,32792,32795],{},[66,32790,32791],{},"uploads use persistent storage",[66,32793,32794],{},"the storage path survives container restarts or server redeploys",[66,32796,32797],{},"file permissions are correct",[16,32799,32800],{},"If user uploads are stored on local disk, confirm the mount or directory is backed up and not tied to disposable releases.",[52,32802,32804],{"id":32803},"_5-check-application-server-configuration","5. Check application server configuration",[16,32806,32807],{},"Your app server should start cleanly before you route live traffic to it.",[16,32809,32810],{},"Gunicorn test start:",[106,32812,32814],{"className":108,"code":32813,"language":110,"meta":111,"style":111},"gunicorn config.wsgi:application --bind 127.0.0.1:8000\n",[20,32815,32816],{"__ignoreMap":111},[115,32817,32818,32820,32823,32825],{"class":117,"line":118},[115,32819,14954],{"class":262},[115,32821,32822],{"class":132}," config.wsgi:application",[115,32824,23605],{"class":202},[115,32826,32827],{"class":132}," 127.0.0.1:8000\n",[16,32829,32830],{},"systemd checks:",[106,32832,32834],{"className":108,"code":32833,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn\nsudo systemctl restart gunicorn\nsudo journalctl -u gunicorn -n 100 --no-pager\n",[20,32835,32836,32846,32856],{"__ignoreMap":111},[115,32837,32838,32840,32842,32844],{"class":117,"line":118},[115,32839,2001],{"class":262},[115,32841,3480],{"class":132},[115,32843,1984],{"class":132},[115,32845,1987],{"class":132},[115,32847,32848,32850,32852,32854],{"class":117,"line":136},[115,32849,2001],{"class":262},[115,32851,3480],{"class":132},[115,32853,3483],{"class":132},[115,32855,1987],{"class":132},[115,32857,32858,32860,32862,32864,32866,32868,32870],{"class":117,"line":149},[115,32859,2001],{"class":262},[115,32861,5030],{"class":132},[115,32863,2788],{"class":202},[115,32865,2791],{"class":132},[115,32867,2794],{"class":202},[115,32869,2797],{"class":202},[115,32871,2800],{"class":202},[16,32873,9761,32874,32876],{},[20,32875,1277],{}," excerpt:",[106,32878,32880],{"className":2026,"code":32879,"language":2028,"meta":111,"style":111},"[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fsrv\u002Fmyapp\u002Fshared\u002F.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3 --timeout 60\nRestart=always\n",[20,32881,32882,32886,32892,32898,32904,32910,32917],{"__ignoreMap":111},[115,32883,32884],{"class":117,"line":118},[115,32885,2060],{"class":262},[115,32887,32888,32890],{"class":117,"line":136},[115,32889,2065],{"class":121},[115,32891,2076],{"class":125},[115,32893,32894,32896],{"class":117,"line":149},[115,32895,2073],{"class":121},[115,32897,2076],{"class":125},[115,32899,32900,32902],{"class":117,"line":162},[115,32901,2081],{"class":121},[115,32903,4905],{"class":125},[115,32905,32906,32908],{"class":117,"line":175},[115,32907,2089],{"class":121},[115,32909,27623],{"class":125},[115,32911,32912,32914],{"class":117,"line":350},[115,32913,2107],{"class":121},[115,32915,32916],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3 --timeout 60\n",[115,32918,32919,32921],{"class":117,"line":365},[115,32920,2116],{"class":121},[115,32922,4932],{"class":125},[16,32924,26735],{},[63,32926,32927,32930,32933,32936],{},[66,32928,32929],{},"worker count is chosen intentionally",[66,32931,32932],{},"timeout is not left at an unsuitable default",[66,32934,32935],{},"restart policy exists",[66,32937,32938,32939,32942],{},"a health endpoint such as ",[20,32940,32941],{},"\u002Fhealthz"," exists if possible",[52,32944,32946],{"id":32945},"_6-check-reverse-proxy-and-tls","6. Check reverse proxy and TLS",[16,32948,32949],{},"Your reverse proxy must route requests correctly and preserve HTTPS information for Django.",[16,32951,32952],{},"Nginx checks:",[106,32954,32956],{"className":108,"code":32955,"language":110,"meta":111,"style":111},"sudo nginx -t\nsudo systemctl reload nginx\ncurl -I https:\u002F\u002Fexample.com\u002F\ncurl https:\u002F\u002Fexample.com\u002Fhealthz\n",[20,32957,32958,32966,32976,32985],{"__ignoreMap":111},[115,32959,32960,32962,32964],{"class":117,"line":118},[115,32961,2001],{"class":262},[115,32963,3906],{"class":132},[115,32965,4282],{"class":202},[115,32967,32968,32970,32972,32974],{"class":117,"line":136},[115,32969,2001],{"class":262},[115,32971,3480],{"class":132},[115,32973,3919],{"class":132},[115,32975,1996],{"class":132},[115,32977,32978,32980,32982],{"class":117,"line":149},[115,32979,2764],{"class":262},[115,32981,2767],{"class":202},[115,32983,32984],{"class":132}," https:\u002F\u002Fexample.com\u002F\n",[115,32986,32987,32989],{"class":117,"line":162},[115,32988,2764],{"class":262},[115,32990,2780],{"class":132},[16,32992,32993],{},"Example Nginx server block:",[106,32995,32997],{"className":2154,"code":32996,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-Port $server_port;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n",[20,32998,32999,33005,33013,33019,33027,33031,33035,33041,33049,33055,33059,33065,33071,33075,33083,33089,33093,33097,33105,33111,33115,33119,33127,33133,33139,33145,33151,33157,33163,33169,33173],{"__ignoreMap":111},[115,33000,33001,33003],{"class":117,"line":118},[115,33002,2163],{"class":121},[115,33004,2166],{"class":125},[115,33006,33007,33009,33011],{"class":117,"line":136},[115,33008,2171],{"class":121},[115,33010,3808],{"class":202},[115,33012,3811],{"class":125},[115,33014,33015,33017],{"class":117,"line":149},[115,33016,2182],{"class":121},[115,33018,3713],{"class":125},[115,33020,33021,33023,33025],{"class":117,"line":162},[115,33022,3822],{"class":121},[115,33024,3825],{"class":202},[115,33026,3828],{"class":125},[115,33028,33029],{"class":117,"line":175},[115,33030,2323],{"class":125},[115,33032,33033],{"class":117,"line":350},[115,33034,310],{"emptyLinePlaceholder":309},[115,33036,33037,33039],{"class":117,"line":365},[115,33038,2163],{"class":121},[115,33040,2166],{"class":125},[115,33042,33043,33045,33047],{"class":117,"line":380},[115,33044,2171],{"class":121},[115,33046,2174],{"class":202},[115,33048,2177],{"class":125},[115,33050,33051,33053],{"class":117,"line":487},[115,33052,2182],{"class":121},[115,33054,3713],{"class":125},[115,33056,33057],{"class":117,"line":2095},[115,33058,310],{"emptyLinePlaceholder":309},[115,33060,33061,33063],{"class":117,"line":2104},[115,33062,2194],{"class":121},[115,33064,2197],{"class":125},[115,33066,33067,33069],{"class":117,"line":2113},[115,33068,2202],{"class":121},[115,33070,2205],{"class":125},[115,33072,33073],{"class":117,"line":2122},[115,33074,310],{"emptyLinePlaceholder":309},[115,33076,33077,33079,33081],{"class":117,"line":2131},[115,33078,2214],{"class":121},[115,33080,2217],{"class":262},[115,33082,2220],{"class":125},[115,33084,33085,33087],{"class":117,"line":2136},[115,33086,2225],{"class":121},[115,33088,15610],{"class":125},[115,33090,33091],{"class":117,"line":2142},[115,33092,2233],{"class":125},[115,33094,33095],{"class":117,"line":2273},[115,33096,310],{"emptyLinePlaceholder":309},[115,33098,33099,33101,33103],{"class":117,"line":2282},[115,33100,2214],{"class":121},[115,33102,2244],{"class":262},[115,33104,2220],{"class":125},[115,33106,33107,33109],{"class":117,"line":2291},[115,33108,2225],{"class":121},[115,33110,15633],{"class":125},[115,33112,33113],{"class":117,"line":2299},[115,33114,2233],{"class":125},[115,33116,33117],{"class":117,"line":2307},[115,33118,310],{"emptyLinePlaceholder":309},[115,33120,33121,33123,33125],{"class":117,"line":2315},[115,33122,2214],{"class":121},[115,33124,2268],{"class":262},[115,33126,2220],{"class":125},[115,33128,33129,33131],{"class":117,"line":2320},[115,33130,2276],{"class":121},[115,33132,3748],{"class":125},[115,33134,33135,33137],{"class":117,"line":7083},[115,33136,2285],{"class":121},[115,33138,2288],{"class":125},[115,33140,33141,33143],{"class":117,"line":7090},[115,33142,2285],{"class":121},[115,33144,2296],{"class":125},[115,33146,33147,33149],{"class":117,"line":7097},[115,33148,2285],{"class":121},[115,33150,7074],{"class":125},[115,33152,33153,33155],{"class":117,"line":7108},[115,33154,2285],{"class":121},[115,33156,2304],{"class":125},[115,33158,33159,33161],{"class":117,"line":7113},[115,33160,2285],{"class":121},[115,33162,2312],{"class":125},[115,33164,33165,33167],{"class":117,"line":16535},[115,33166,2285],{"class":121},[115,33168,3767],{"class":125},[115,33170,33171],{"class":117,"line":16544},[115,33172,2233],{"class":125},[115,33174,33175],{"class":117,"line":16549},[115,33176,2323],{"class":125},[16,33178,33179],{},"In Django, review:",[106,33181,33183],{"className":2369,"code":33182,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_SSL_REDIRECT = True\n",[20,33184,33185,33201],{"__ignoreMap":111},[115,33186,33187,33189,33191,33193,33195,33197,33199],{"class":117,"line":118},[115,33188,2377],{"class":202},[115,33190,2380],{"class":121},[115,33192,2383],{"class":125},[115,33194,2386],{"class":132},[115,33196,1153],{"class":125},[115,33198,2391],{"class":132},[115,33200,2394],{"class":125},[115,33202,33203,33205,33207],{"class":117,"line":136},[115,33204,2407],{"class":202},[115,33206,2380],{"class":121},[115,33208,2412],{"class":202},[16,33210,4038,33211,33213,33214,33216],{},[20,33212,2377],{}," if your reverse proxy is trusted to set and sanitize ",[20,33215,3203],{},". Do not trust client-supplied forwarding headers directly.",[16,33218,33219],{},"Also verify secure behavior from the browser side:",[63,33221,33222,33224,33227,33230],{},[66,33223,13434],{},[66,33225,33226],{},"secure cookies are set over HTTPS",[66,33228,33229],{},"login and CSRF-protected forms work behind the proxy",[66,33231,33232],{},"redirect loops do not occur",[16,33234,33235,33236,33238],{},"If your deployment depends on forwarded host headers, review whether ",[20,33237,13891],{}," is actually needed. Do not enable it unless your proxy setup requires it.",[52,33240,33242],{"id":33241},"_7-harden-django-security-settings","7. Harden Django security settings",[16,33244,33245],{},"At minimum, review these settings:",[106,33247,33249],{"className":2369,"code":33248,"language":1114,"meta":111,"style":111},"SECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n",[20,33250,33251,33259,33267,33275,33291,33299,33307],{"__ignoreMap":111},[115,33252,33253,33255,33257],{"class":117,"line":118},[115,33254,2407],{"class":202},[115,33256,2380],{"class":121},[115,33258,2412],{"class":202},[115,33260,33261,33263,33265],{"class":117,"line":136},[115,33262,2417],{"class":202},[115,33264,2380],{"class":121},[115,33266,2412],{"class":202},[115,33268,33269,33271,33273],{"class":117,"line":149},[115,33270,2426],{"class":202},[115,33272,2380],{"class":121},[115,33274,2412],{"class":202},[115,33276,33277,33279,33281,33283,33285,33287,33289],{"class":117,"line":162},[115,33278,2377],{"class":202},[115,33280,2380],{"class":121},[115,33282,2383],{"class":125},[115,33284,2386],{"class":132},[115,33286,1153],{"class":125},[115,33288,2391],{"class":132},[115,33290,2394],{"class":125},[115,33292,33293,33295,33297],{"class":117,"line":175},[115,33294,7440],{"class":202},[115,33296,2380],{"class":121},[115,33298,11991],{"class":202},[115,33300,33301,33303,33305],{"class":117,"line":350},[115,33302,7464],{"class":202},[115,33304,2380],{"class":121},[115,33306,2412],{"class":202},[115,33308,33309,33311,33313],{"class":117,"line":365},[115,33310,12004],{"class":202},[115,33312,2380],{"class":121},[115,33314,7355],{"class":202},[16,33316,33317,33318,33320],{},"Enable HSTS only after HTTPS is working correctly for the domain, and be careful with ",[20,33319,7464],{}," if not all subdomains are HTTPS-ready.",[16,33322,33323],{},"Also check:",[106,33325,33327],{"className":2369,"code":33326,"language":1114,"meta":111,"style":111},"CSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\", \"https:\u002F\u002Fwww.example.com\"]\n",[20,33328,33329],{"__ignoreMap":111},[115,33330,33331,33333,33335,33337,33339,33341,33343],{"class":117,"line":118},[115,33332,2725],{"class":202},[115,33334,2380],{"class":121},[115,33336,7493],{"class":125},[115,33338,15110],{"class":132},[115,33340,1153],{"class":125},[115,33342,15115],{"class":132},[115,33344,2552],{"class":125},[16,33346,33347],{},"And review:",[63,33349,33350,33353,33356],{},[66,33351,33352],{},"admin exposure is intentional",[66,33354,33355],{},"unnecessary debug tools are removed",[66,33357,33358],{},"CSRF and session settings match your real HTTPS origins",[16,33360,33361],{},"Run:",[106,33363,33364],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,33365,33366],{"__ignoreMap":111},[115,33367,33368,33370,33372,33374],{"class":117,"line":118},[115,33369,1114],{"class":262},[115,33371,1117],{"class":132},[115,33373,1814],{"class":132},[115,33375,1817],{"class":202},[52,33377,33379],{"id":33378},"_8-run-pre-deployment-verification","8. Run pre-deployment verification",[16,33381,33382],{},"Before release, run a short verification block:",[106,33384,33386],{"className":108,"code":33385,"language":110,"meta":111,"style":111},"python manage.py check --deploy\npython manage.py showmigrations\npython manage.py collectstatic --noinput\n",[20,33387,33388,33398,33406],{"__ignoreMap":111},[115,33389,33390,33392,33394,33396],{"class":117,"line":118},[115,33391,1114],{"class":262},[115,33393,1117],{"class":132},[115,33395,1814],{"class":132},[115,33397,1817],{"class":202},[115,33399,33400,33402,33404],{"class":117,"line":136},[115,33401,1114],{"class":262},[115,33403,1117],{"class":132},[115,33405,1129],{"class":132},[115,33407,33408,33410,33412,33414],{"class":117,"line":149},[115,33409,1114],{"class":262},[115,33411,1117],{"class":132},[115,33413,1838],{"class":132},[115,33415,1841],{"class":202},[16,33417,22752],{},[63,33419,33420,33423,33426,33429],{},[66,33421,33422],{},"dependency installation succeeds",[66,33424,33425],{},"required environment variables exist",[66,33427,33428],{},"external services are reachable",[66,33430,33431],{},"release artifact or image tag is correct",[16,33433,5244],{},[106,33435,33437],{"className":108,"code":33436,"language":110,"meta":111,"style":111},"docker compose ps\ndocker compose logs web --tail=100\ndocker compose exec web python manage.py check --deploy\n",[20,33438,33439,33447,33459],{"__ignoreMap":111},[115,33440,33441,33443,33445],{"class":117,"line":118},[115,33442,3295],{"class":262},[115,33444,3298],{"class":132},[115,33446,4790],{"class":132},[115,33448,33449,33451,33453,33455,33457],{"class":117,"line":136},[115,33450,3295],{"class":262},[115,33452,3298],{"class":132},[115,33454,3301],{"class":132},[115,33456,3304],{"class":132},[115,33458,3307],{"class":202},[115,33460,33461,33463,33465,33467,33469,33471,33473,33475],{"class":117,"line":149},[115,33462,3295],{"class":262},[115,33464,3298],{"class":132},[115,33466,5258],{"class":132},[115,33468,3304],{"class":132},[115,33470,19676],{"class":132},[115,33472,1117],{"class":132},[115,33474,1814],{"class":132},[115,33476,1817],{"class":202},[52,33478,33480],{"id":33479},"_9-plan-the-deployment-sequence","9. Plan the deployment sequence",[16,33482,33483],{},"A simple safe sequence is:",[1173,33485,33486,33489,33492,33494,33498,33501,33504],{},[66,33487,33488],{},"pull code or deploy image",[66,33490,33491],{},"install dependencies if needed",[66,33493,1186],{},[66,33495,7902,33496],{},[20,33497,13689],{},[66,33499,33500],{},"restart or reload app process",[66,33502,33503],{},"confirm web health",[66,33505,33506],{},"confirm workers and scheduled jobs",[16,33508,33509],{},"Non-Docker example:",[106,33511,33513],{"className":108,"code":33512,"language":110,"meta":111,"style":111},"python manage.py migrate\npython manage.py collectstatic --noinput\nsudo systemctl restart gunicorn\nsudo systemctl status gunicorn\ncurl https:\u002F\u002Fexample.com\u002Fhealthz\n",[20,33514,33515,33523,33533,33543,33553],{"__ignoreMap":111},[115,33516,33517,33519,33521],{"class":117,"line":118},[115,33518,1114],{"class":262},[115,33520,1117],{"class":132},[115,33522,11324],{"class":132},[115,33524,33525,33527,33529,33531],{"class":117,"line":136},[115,33526,1114],{"class":262},[115,33528,1117],{"class":132},[115,33530,1838],{"class":132},[115,33532,1841],{"class":202},[115,33534,33535,33537,33539,33541],{"class":117,"line":149},[115,33536,2001],{"class":262},[115,33538,3480],{"class":132},[115,33540,3483],{"class":132},[115,33542,1987],{"class":132},[115,33544,33545,33547,33549,33551],{"class":117,"line":162},[115,33546,2001],{"class":262},[115,33548,3480],{"class":132},[115,33550,1984],{"class":132},[115,33552,1987],{"class":132},[115,33554,33555,33557],{"class":117,"line":175},[115,33556,2764],{"class":262},[115,33558,2780],{"class":132},[16,33560,33561],{},"Docker example:",[106,33563,33565],{"className":108,"code":33564,"language":110,"meta":111,"style":111},"docker compose pull\ndocker compose up -d db redis\ndocker compose run --rm web python manage.py migrate\ndocker compose run --rm web python manage.py collectstatic --noinput\ndocker compose up -d web\ndocker compose logs web --tail=100\n",[20,33566,33567,33575,33589,33607,33627,33639],{"__ignoreMap":111},[115,33568,33569,33571,33573],{"class":117,"line":118},[115,33570,3295],{"class":262},[115,33572,3298],{"class":132},[115,33574,13528],{"class":132},[115,33576,33577,33579,33581,33583,33585,33587],{"class":117,"line":136},[115,33578,3295],{"class":262},[115,33580,3298],{"class":132},[115,33582,3502],{"class":132},[115,33584,1019],{"class":202},[115,33586,26901],{"class":132},[115,33588,8625],{"class":132},[115,33590,33591,33593,33595,33597,33599,33601,33603,33605],{"class":117,"line":149},[115,33592,3295],{"class":262},[115,33594,3298],{"class":132},[115,33596,18889],{"class":132},[115,33598,18892],{"class":202},[115,33600,3304],{"class":132},[115,33602,19676],{"class":132},[115,33604,1117],{"class":132},[115,33606,11324],{"class":132},[115,33608,33609,33611,33613,33615,33617,33619,33621,33623,33625],{"class":117,"line":162},[115,33610,3295],{"class":262},[115,33612,3298],{"class":132},[115,33614,18889],{"class":132},[115,33616,18892],{"class":202},[115,33618,3304],{"class":132},[115,33620,19676],{"class":132},[115,33622,1117],{"class":132},[115,33624,1838],{"class":132},[115,33626,1841],{"class":202},[115,33628,33629,33631,33633,33635,33637],{"class":117,"line":175},[115,33630,3295],{"class":262},[115,33632,3298],{"class":132},[115,33634,3502],{"class":132},[115,33636,1019],{"class":202},[115,33638,3510],{"class":132},[115,33640,33641,33643,33645,33647,33649],{"class":117,"line":350},[115,33642,3295],{"class":262},[115,33644,3298],{"class":132},[115,33646,3301],{"class":132},[115,33648,3304],{"class":132},[115,33650,3307],{"class":202},[16,33652,3632,33653,33655],{},[20,33654,25052],{}," before migrations is safe. Make the order explicit so new app containers do not start against an incompatible schema.",[16,33657,33658],{},"If your deployment process depends on migrations completing before app restart, keep that order explicit and documented.",[52,33660,33662],{"id":33661},"_10-confirm-observability-and-logging","10. Confirm observability and logging",[16,33664,33665],{},"At minimum, make sure you can access:",[63,33667,33668,33671,33674,33677,33680],{},[66,33669,33670],{},"Django application logs",[66,33672,33673],{},"Gunicorn or Uvicorn logs",[66,33675,33676],{},"Nginx or Caddy logs",[66,33678,33679],{},"error tracking if configured",[66,33681,33682],{},"uptime or health monitoring",[16,33684,8572],{},[106,33686,33688],{"className":108,"code":33687,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 100 --no-pager\ndocker compose logs web --tail=100\n",[20,33689,33690,33706],{"__ignoreMap":111},[115,33691,33692,33694,33696,33698,33700,33702,33704],{"class":117,"line":118},[115,33693,2001],{"class":262},[115,33695,5030],{"class":132},[115,33697,2788],{"class":202},[115,33699,2791],{"class":132},[115,33701,2794],{"class":202},[115,33703,2797],{"class":202},[115,33705,2800],{"class":202},[115,33707,33708,33710,33712,33714,33716],{"class":117,"line":136},[115,33709,3295],{"class":262},[115,33711,3298],{"class":132},[115,33713,3301],{"class":132},[115,33715,3304],{"class":132},[115,33717,3307],{"class":202},[16,33719,33720],{},"If you cannot inspect logs quickly during a release, production debugging will be slow.",[52,33722,33724],{"id":33723},"_11-verify-background-jobs-and-async-components","11. Verify background jobs and async components",[16,33726,33727],{},"If you use Celery, Redis, Channels, or scheduled jobs, include them in the release checklist.",[16,33729,26735],{},[63,33731,33732,33735,33738,33741],{},[66,33733,33734],{},"Celery workers start",[66,33736,33737],{},"Celery beat is enabled if used",[66,33739,33740],{},"Redis is reachable",[66,33742,33743],{},"retry behavior and queue backlog are understood",[16,33745,33746],{},"A deployment is not complete if the web app is healthy but background jobs are dead.",[52,33748,33750],{"id":33749},"_12-prepare-rollback-and-recovery-steps","12. Prepare rollback and recovery steps",[16,33752,33753],{},"Before deploying, define:",[63,33755,33756,33759,33762,33765],{},[66,33757,33758],{},"how to restore the previous release",[66,33760,33761],{},"how to redeploy a previous image tag or release directory",[66,33763,33764],{},"whether the new code is compatible with current database schema",[66,33766,33767],{},"who decides rollback",[16,33769,33770],{},"Important: code rollback may not fully undo a migration. If the migration changed schema in a non-backward-compatible way, rollback may require a separate database plan.",[52,33772,33774],{"id":33773},"_13-run-post-deployment-checks","13. Run post-deployment checks",[16,33776,33777],{},"After release, smoke test the app:",[63,33779,33780,33782,33785,33788,33791,33794,33797],{},[66,33781,24517],{},[66,33783,33784],{},"login works",[66,33786,33787],{},"admin login works if enabled",[66,33789,33790],{},"a database write succeeds",[66,33792,33793],{},"CSS and JS load correctly",[66,33795,33796],{},"one critical user flow completes",[66,33798,33799],{},"background workers are processing jobs",[16,33801,4195],{},[106,33803,33805],{"className":108,"code":33804,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002F\ncurl https:\u002F\u002Fexample.com\u002Fhealthz\n",[20,33806,33807,33815],{"__ignoreMap":111},[115,33808,33809,33811,33813],{"class":117,"line":118},[115,33810,2764],{"class":262},[115,33812,2767],{"class":202},[115,33814,32984],{"class":132},[115,33816,33817,33819],{"class":117,"line":136},[115,33818,2764],{"class":262},[115,33820,2780],{"class":132},[23099,33822],{},[11,33824,33826],{"id":33825},"example-production-verification-workflow","Example production verification workflow",[52,33828,33830],{"id":33829},"manual-checklist-for-a-non-docker-server","Manual checklist for a non-Docker server",[1173,33832,33833,33836,33841,33844,33848,33851,33855,33859,33861,33864,33867],{},[66,33834,33835],{},"SSH into the server",[66,33837,4183,33838,33840],{},[20,33839,191],{}," or secret source is updated",[66,33842,33843],{},"activate the virtualenv",[66,33845,7902,33846],{},[20,33847,20349],{},[66,33849,33850],{},"review migrations",[66,33852,7902,33853],{},[20,33854,10296],{},[66,33856,7902,33857],{},[20,33858,13689],{},[66,33860,22673],{},[66,33862,33863],{},"test Nginx config and reload if changed",[66,33865,33866],{},"smoke test the live site",[66,33868,33869],{},"watch logs for a few minutes",[52,33871,33873],{"id":33872},"manual-checklist-for-a-docker-based-deployment","Manual checklist for a Docker-based deployment",[1173,33875,33876,33879,33882,33887,33890,33895,33900,33903,33906,33909],{},[66,33877,33878],{},"confirm image tag or release commit",[66,33880,33881],{},"confirm environment variables for containers",[66,33883,33884],{},[20,33885,33886],{},"docker compose pull",[66,33888,33889],{},"start only required dependencies first if needed",[66,33891,7902,33892,33894],{},[20,33893,10296],{}," in a one-off web container or controlled release step",[66,33896,7902,33897,33899],{},[20,33898,13689],{}," if your setup requires it",[66,33901,33902],{},"start or update the web container",[66,33904,33905],{},"inspect container logs",[66,33907,33908],{},"test HTTPS and health endpoints",[66,33910,33911],{},"verify worker containers separately",[52,33913,33915],{"id":33914},"where-teams-usually-miss-production-checks","Where teams usually miss production checks",[16,33917,33918],{},"Common misses:",[63,33920,33921,33926,33929,33932,33935,33938],{},[66,33922,33923,33925],{},[20,33924,2719],{}," not updated",[66,33927,33928],{},"no persistent media storage",[66,33930,33931],{},"migrations applied without review",[66,33933,33934],{},"TLS works externally but proxy headers are wrong internally",[66,33936,33937],{},"Celery not restarted with the release",[66,33939,33940],{},"no tested rollback path",[1850,33942,33944],{"id":33943},"when-to-convert-this-process-into-scripts-or-templates","When to convert this process into scripts or templates",[16,33946,33947,33948,33950,33951,33953,33954,33956],{},"Once your release steps become repetitive, script the checks in the same order every time. Good first targets are environment validation, ",[20,33949,20349],{},", migration execution, ",[20,33952,13689],{},", service restarts, and smoke tests. Reusable templates also help for Django settings, ",[20,33955,1277],{}," units, reverse proxy configs, and CI\u002FCD workflows.",[23099,33958],{},[11,33960,1321],{"id":1320},[52,33962,33964],{"id":33963},"why-this-checklist-is-ordered-by-deployment-risk","Why this checklist is ordered by deployment risk",[16,33966,33967],{},"The highest-risk items come first: settings, secrets, database state, and TLS. If any of those are wrong, the deployment can fail completely or expose data. Static assets, workers, and monitoring matter too, but they are easier to diagnose after the core release path is safe.",[52,33969,33971],{"id":33970},"why-migrations-secrets-and-tls-should-be-verified-before-release","Why migrations, secrets, and TLS should be verified before release",[16,33973,33974],{},"Migrations can lock tables or make old code incompatible. Secrets can stop the app from starting or expose production systems. TLS and proxy header mistakes can break redirects, secure cookies, CSRF validation, and login behavior.",[52,33976,33978],{"id":33977},"why-rollback-must-be-planned-before-the-deployment-starts","Why rollback must be planned before the deployment starts",[16,33980,33981],{},"Rollback is a release requirement, not a cleanup step. If you only think about recovery after a failed deployment, you are already in incident mode.",[23099,33983],{},[11,33985,10095],{"id":10094},[52,33987,33989],{"id":33988},"deploying-behind-a-load-balancer-or-platform-proxy","Deploying behind a load balancer or platform proxy",[16,33991,33992,33993,211],{},"If Django is behind Nginx, Caddy, a cloud load balancer, or another proxy, confirm forwarded protocol headers are set correctly and Django trusts the right header via ",[20,33994,2377],{},[16,33996,33997,33998,34000],{},"If your app needs original host preservation through multiple proxy layers, review ",[20,33999,12021],{}," carefully rather than enabling it by default.",[52,34002,34004],{"id":34003},"zero-downtime-vs-simple-restart-deployments","Zero-downtime vs simple restart deployments",[16,34006,34007],{},"A small app can often tolerate a short restart. For larger apps, use a deployment model that keeps old processes serving traffic until new ones pass health checks. Do not assume zero downtime if migrations are blocking.",[52,34009,34011],{"id":34010},"handling-apps-with-user-uploads","Handling apps with user uploads",[16,34013,34014],{},"Treat media as persistent data. Do not store uploads inside a release directory that gets replaced on deploy. Use a shared mount or object storage.",[52,34016,34018],{"id":34017},"multi-service-apps-with-celery-redis-or-websockets","Multi-service apps with Celery, Redis, or WebSockets",[16,34020,34021],{},"Include every service in the checklist. A healthy Django web process is only one part of the system if your app also depends on workers, schedulers, Redis, or ASGI or WebSocket components.",[23099,34023],{},[11,34025,1386],{"id":1385},[16,34027,34028,34029,211],{},"For production settings, see ",[1395,34030,3007],{"href":3006},[16,34032,34033,34034,211],{},"For deployment stack decisions, see ",[1395,34035,3014],{"href":3013},[16,34037,34038,34039,211],{},"For file handling in production, see ",[1395,34040,34041],{"href":2978},"Django Static vs Media Files in Production",[16,34043,34044,34045,3146,34047,211],{},"For step-by-step server deployment examples, see ",[1395,34046,32215],{"href":2985},[1395,34048,34049],{"href":2992},"deploy Django with Docker Compose",[16,34051,34052,34053,211],{},"For recovery planning, see ",[1395,34054,34055],{"href":22931},"Django deployment rollbacks and recovery",[23099,34057],{},[11,34059,1420],{"id":1419},[52,34061,34063],{"id":34062},"what-is-the-minimum-django-deployment-checklist-for-a-small-app","What is the minimum Django deployment checklist for a small app?",[16,34065,34066,34067,34069,34070,34072,34073,34075],{},"At minimum: ",[20,34068,32431],{},", correct ",[20,34071,2719],{},", secrets from environment variables, PostgreSQL backups, HTTPS enabled, ",[20,34074,20349],{},", reviewed migrations, working static files, accessible logs, and a basic rollback plan.",[52,34077,34079],{"id":34078},"should-i-run-migrations-before-or-after-restarting-gunicorn","Should I run migrations before or after restarting Gunicorn?",[16,34081,34082],{},"Usually before restarting Gunicorn, so the new process starts against the expected schema. But the safest order depends on whether the release is backward-compatible with the current database state. For risky schema changes, plan the deployment carefully rather than relying on a default order.",[52,34084,34086],{"id":34085},"what-is-the-safest-way-to-store-django-production-secrets","What is the safest way to store Django production secrets?",[16,34088,34089],{},"Use a secret manager if available. If not, use environment variables or a protected environment file outside the repository, with restricted permissions and a documented rotation process.",[52,34091,34093],{"id":34092},"what-should-i-verify-immediately-after-deploying-django","What should I verify immediately after deploying Django?",[16,34095,34096],{},"Check HTTPS, homepage response, login, one write to the database, static asset loading, health endpoint response, worker status, and recent application logs.",[52,34098,34100],{"id":34099},"do-small-django-apps-still-need-tls-backups-and-monitoring","Do small Django apps still need TLS, backups, and monitoring?",[16,34102,34103],{},"Yes. Small apps fail in the same ways as larger ones. TLS protects sessions and logins, backups protect your data, and basic monitoring helps you detect failures quickly.",[1485,34105,34106],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":111,"searchDepth":149,"depth":149,"links":34108},[34109,34113,34116,34131,34138,34143,34149,34150],{"id":13,"depth":136,"text":14,"children":34110},[34111,34112],{"id":32369,"depth":149,"text":32370},{"id":32399,"depth":149,"text":32400},{"id":29,"depth":136,"text":30,"children":34114},[34115],{"id":32468,"depth":149,"text":32469},{"id":32494,"depth":136,"text":32365,"children":34117},[34118,34119,34120,34121,34122,34123,34124,34125,34126,34127,34128,34129,34130],{"id":32497,"depth":149,"text":32498},{"id":32620,"depth":149,"text":32621},{"id":32677,"depth":149,"text":32678},{"id":32746,"depth":149,"text":32747},{"id":32803,"depth":149,"text":32804},{"id":32945,"depth":149,"text":32946},{"id":33241,"depth":149,"text":33242},{"id":33378,"depth":149,"text":33379},{"id":33479,"depth":149,"text":33480},{"id":33661,"depth":149,"text":33662},{"id":33723,"depth":149,"text":33724},{"id":33749,"depth":149,"text":33750},{"id":33773,"depth":149,"text":33774},{"id":33825,"depth":136,"text":33826,"children":34132},[34133,34134,34135],{"id":33829,"depth":149,"text":33830},{"id":33872,"depth":149,"text":33873},{"id":33914,"depth":149,"text":33915,"children":34136},[34137],{"id":33943,"depth":162,"text":33944},{"id":1320,"depth":136,"text":1321,"children":34139},[34140,34141,34142],{"id":33963,"depth":149,"text":33964},{"id":33970,"depth":149,"text":33971},{"id":33977,"depth":149,"text":33978},{"id":10094,"depth":136,"text":10095,"children":34144},[34145,34146,34147,34148],{"id":33988,"depth":149,"text":33989},{"id":34003,"depth":149,"text":34004},{"id":34010,"depth":149,"text":34011},{"id":34017,"depth":149,"text":34018},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":34151},[34152,34153,34154,34155,34156],{"id":34062,"depth":149,"text":34063},{"id":34078,"depth":149,"text":34079},{"id":34085,"depth":149,"text":34086},{"id":34092,"depth":149,"text":34093},{"id":34099,"depth":149,"text":34100},"Checklist","A Django app that works locally is not automatically safe to run in production. Most deployment failures come from a small set of issues: wrong settings, missing secrets, unsafe...",{},"\u002Fdjango-deployment-checklist",[3006,3013,2978],"authority","production-checklist",{"title":3000,"description":34158},[1557],"django-deployment-checklist",[1557],"JXxQI5yVC07tDl9vqchc5z9dJUDXCuQ6BItbWpChfhM",{"id":34170,"title":34171,"body":34172,"category":1541,"description":35625,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":35626,"navigation":309,"path":35627,"priority":35628,"related":35629,"role":34162,"section":1554,"seo":35632,"stack":35633,"stem":35634,"tags":35635,"type":3104,"__hash__":35636},"articles\u002Fdjango-deployment-security-basics.md","Django Deployment Security Basics You Should Not Skip",{"type":8,"value":34173,"toc":35598},[34174,34176,34185,34188,34195,34197,34200,34240,34243,34257,34260,34262,34266,34269,34289,34295,34299,34302,34574,34577,34607,34609,34623,34631,34635,34638,34641,34840,34842,34862,34865,34868,34880,34888,34892,34895,34910,34913,34915,34972,34975,35002,35005,35008,35092,35095,35106,35108,35140,35144,35147,35149,35160,35163,35166,35206,35209,35214,35220,35223,35238,35241,35245,35248,35262,35265,35282,35285,35299,35303,35306,35309,35323,35326,35348,35351,35354,35358,35366,35370,35373,35387,35390,35404,35407,35418,35420,35423,35446,35449,35451,35500,35503,35517,35519,35525,35531,35537,35542,35544,35548,35557,35561,35564,35571,35574,35581,35584,35588,35596],[11,34175,14],{"id":13},[16,34177,34178,34179,34181,34182,34184],{},"A lot of Django apps reach production with local-development assumptions still in place: ",[20,34180,24957],{},", weak host validation, secrets in ",[20,34183,191],{}," files with broad permissions, Gunicorn listening on a public port, or HTTPS handled inconsistently between Django and the reverse proxy.",[16,34186,34187],{},"That usually works until the app gets real traffic. Then small misconfigurations turn into avoidable security problems: detailed tracebacks exposed to users, session cookies sent over HTTP, leaked credentials, admin access from the public internet, or a server with more open ports than it needs.",[16,34189,34190,34191,34194],{},"This page covers a practical baseline for ",[1226,34192,34193],{},"Django deployment security"," for a single production app on Linux. It is not a full security architecture guide. It is the minimum set of controls you should apply before sending production traffic to Django.",[11,34196,30],{"id":29},[16,34198,34199],{},"Before go-live, make sure you have all of the following in place:",[63,34201,34202,34206,34211,34214,34219,34222,34225,34228,34231,34234,34237],{},[66,34203,34204],{},[20,34205,32431],{},[66,34207,34208,34210],{},[20,34209,2719],{}," restricted to real domains",[66,34212,34213],{},"HTTPS enabled, with HTTP redirected to HTTPS",[66,34215,34216,34218],{},[20,34217,2713],{}," and credentials loaded from environment or a secret store",[66,34220,34221],{},"secure cookie and header settings enabled",[66,34223,34224],{},"Django running behind Gunicorn\u002FUvicorn and Nginx or Caddy",[66,34226,34227],{},"Gunicorn\u002FUvicorn bound only to localhost or a Unix socket",[66,34229,34230],{},"firewall rules allowing only required ports",[66,34232,34233],{},"SSH hardened with key-based access",[66,34235,34236],{},"dependencies and OS packages patched",[66,34238,34239],{},"backups and a rollback path verified",[16,34241,34242],{},"A good first validation step is:",[106,34244,34245],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,34246,34247],{"__ignoreMap":111},[115,34248,34249,34251,34253,34255],{"class":117,"line":118},[115,34250,1114],{"class":262},[115,34252,1117],{"class":132},[115,34254,1814],{"class":132},[115,34256,1817],{"class":202},[16,34258,34259],{},"That will not secure everything for you, but it catches several common Django production security mistakes.",[11,34261,43],{"id":42},[11,34263,34265],{"id":34264},"_1-start-with-a-production-threat-model","1) Start with a production threat model",[16,34267,34268],{},"This baseline protects against:",[63,34270,34271,34274,34277,34280,34283,34286],{},[66,34272,34273],{},"accidental data exposure from debug settings",[66,34275,34276],{},"leaked secrets in source control or world-readable files",[66,34278,34279],{},"insecure transport over plain HTTP",[66,34281,34282],{},"weak host and proxy configuration",[66,34284,34285],{},"missing cookie and browser header protections",[66,34287,34288],{},"unnecessary server and admin exposure",[16,34290,34291,34292,34294],{},"It does ",[1226,34293,7474],{}," fully cover advanced WAF design, deep container isolation, compliance programs, or large multi-region architectures.",[11,34296,34298],{"id":34297},"_2-lock-down-django-production-settings","2) Lock down Django production settings",[16,34300,34301],{},"A minimal production settings example:",[106,34303,34305],{"className":2369,"code":34304,"language":1114,"meta":111,"style":111},"import os\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = False\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\n\nALLOWED_HOSTS = [\n    \"example.com\",\n    \"www.example.com\",\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fexample.com\",\n    \"https:\u002F\u002Fwww.example.com\",\n]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSESSION_COOKIE_HTTPONLY = True\nCSRF_COOKIE_HTTPONLY = False\nCSRF_COOKIE_SAMESITE = \"Lax\"\nSESSION_COOKIE_SAMESITE = \"Lax\"\n\nSECURE_SSL_REDIRECT = False  # prefer proxy-level redirect when using Nginx; only safe if Django is not publicly reachable except through the proxy\nSECURE_HSTS_SECONDS = 300\nSECURE_HSTS_INCLUDE_SUBDOMAINS = False\nSECURE_HSTS_PRELOAD = False\n\nSECURE_CONTENT_TYPE_NOSNIFF = True\nX_FRAME_OPTIONS = \"DENY\"\nSECURE_REFERRER_POLICY = \"strict-origin-when-cross-origin\"\n",[20,34306,34307,34313,34323,34327,34340,34344,34352,34356,34368,34372,34380,34386,34392,34396,34400,34408,34414,34420,34424,34428,34444,34448,34456,34464,34473,34482,34492,34501,34505,34517,34525,34533,34541,34545,34554,34564],{"__ignoreMap":111},[115,34308,34309,34311],{"class":117,"line":118},[115,34310,5613],{"class":121},[115,34312,5616],{"class":125},[115,34314,34315,34317,34319,34321],{"class":117,"line":136},[115,34316,5621],{"class":121},[115,34318,16353],{"class":125},[115,34320,5613],{"class":121},[115,34322,16358],{"class":125},[115,34324,34325],{"class":117,"line":149},[115,34326,310],{"emptyLinePlaceholder":309},[115,34328,34329,34331,34333,34335,34337],{"class":117,"line":162},[115,34330,16374],{"class":202},[115,34332,2380],{"class":121},[115,34334,16379],{"class":125},[115,34336,16382],{"class":202},[115,34338,34339],{"class":125},").resolve().parent.parent\n",[115,34341,34342],{"class":117,"line":175},[115,34343,310],{"emptyLinePlaceholder":309},[115,34345,34346,34348,34350],{"class":117,"line":350},[115,34347,7350],{"class":202},[115,34349,2380],{"class":121},[115,34351,7355],{"class":202},[115,34353,34354],{"class":117,"line":365},[115,34355,310],{"emptyLinePlaceholder":309},[115,34357,34358,34360,34362,34364,34366],{"class":117,"line":380},[115,34359,2713],{"class":202},[115,34361,2380],{"class":121},[115,34363,8861],{"class":125},[115,34365,12063],{"class":132},[115,34367,2552],{"class":125},[115,34369,34370],{"class":117,"line":487},[115,34371,310],{"emptyLinePlaceholder":309},[115,34373,34374,34376,34378],{"class":117,"line":2095},[115,34375,2719],{"class":202},[115,34377,2380],{"class":121},[115,34379,3540],{"class":125},[115,34381,34382,34384],{"class":117,"line":2104},[115,34383,3545],{"class":132},[115,34385,3354],{"class":125},[115,34387,34388,34390],{"class":117,"line":2113},[115,34389,3552],{"class":132},[115,34391,3354],{"class":125},[115,34393,34394],{"class":117,"line":2122},[115,34395,2552],{"class":125},[115,34397,34398],{"class":117,"line":2131},[115,34399,310],{"emptyLinePlaceholder":309},[115,34401,34402,34404,34406],{"class":117,"line":2136},[115,34403,2725],{"class":202},[115,34405,2380],{"class":121},[115,34407,3540],{"class":125},[115,34409,34410,34412],{"class":117,"line":2142},[115,34411,3582],{"class":132},[115,34413,3354],{"class":125},[115,34415,34416,34418],{"class":117,"line":2273},[115,34417,3589],{"class":132},[115,34419,3354],{"class":125},[115,34421,34422],{"class":117,"line":2282},[115,34423,2552],{"class":125},[115,34425,34426],{"class":117,"line":2291},[115,34427,310],{"emptyLinePlaceholder":309},[115,34429,34430,34432,34434,34436,34438,34440,34442],{"class":117,"line":2299},[115,34431,2377],{"class":202},[115,34433,2380],{"class":121},[115,34435,2383],{"class":125},[115,34437,2386],{"class":132},[115,34439,1153],{"class":125},[115,34441,2391],{"class":132},[115,34443,2394],{"class":125},[115,34445,34446],{"class":117,"line":2307},[115,34447,310],{"emptyLinePlaceholder":309},[115,34449,34450,34452,34454],{"class":117,"line":2315},[115,34451,2417],{"class":202},[115,34453,2380],{"class":121},[115,34455,2412],{"class":202},[115,34457,34458,34460,34462],{"class":117,"line":2320},[115,34459,2426],{"class":202},[115,34461,2380],{"class":121},[115,34463,2412],{"class":202},[115,34465,34466,34469,34471],{"class":117,"line":7083},[115,34467,34468],{"class":202},"SESSION_COOKIE_HTTPONLY",[115,34470,2380],{"class":121},[115,34472,2412],{"class":202},[115,34474,34475,34478,34480],{"class":117,"line":7090},[115,34476,34477],{"class":202},"CSRF_COOKIE_HTTPONLY",[115,34479,2380],{"class":121},[115,34481,7355],{"class":202},[115,34483,34484,34487,34489],{"class":117,"line":7097},[115,34485,34486],{"class":202},"CSRF_COOKIE_SAMESITE",[115,34488,2380],{"class":121},[115,34490,34491],{"class":132}," \"Lax\"\n",[115,34493,34494,34497,34499],{"class":117,"line":7108},[115,34495,34496],{"class":202},"SESSION_COOKIE_SAMESITE",[115,34498,2380],{"class":121},[115,34500,34491],{"class":132},[115,34502,34503],{"class":117,"line":7113},[115,34504,310],{"emptyLinePlaceholder":309},[115,34506,34507,34509,34511,34514],{"class":117,"line":16535},[115,34508,2407],{"class":202},[115,34510,2380],{"class":121},[115,34512,34513],{"class":202}," False",[115,34515,34516],{"class":3861},"  # prefer proxy-level redirect when using Nginx; only safe if Django is not publicly reachable except through the proxy\n",[115,34518,34519,34521,34523],{"class":117,"line":16544},[115,34520,7440],{"class":202},[115,34522,2380],{"class":121},[115,34524,7445],{"class":202},[115,34526,34527,34529,34531],{"class":117,"line":16549},[115,34528,7464],{"class":202},[115,34530,2380],{"class":121},[115,34532,7355],{"class":202},[115,34534,34535,34537,34539],{"class":117,"line":16555},[115,34536,12004],{"class":202},[115,34538,2380],{"class":121},[115,34540,7355],{"class":202},[115,34542,34543],{"class":117,"line":16564},[115,34544,310],{"emptyLinePlaceholder":309},[115,34546,34547,34550,34552],{"class":117,"line":16573},[115,34548,34549],{"class":202},"SECURE_CONTENT_TYPE_NOSNIFF",[115,34551,2380],{"class":121},[115,34553,2412],{"class":202},[115,34555,34556,34559,34561],{"class":117,"line":16582},[115,34557,34558],{"class":202},"X_FRAME_OPTIONS",[115,34560,2380],{"class":121},[115,34562,34563],{"class":132}," \"DENY\"\n",[115,34565,34566,34569,34571],{"class":117,"line":16587},[115,34567,34568],{"class":202},"SECURE_REFERRER_POLICY",[115,34570,2380],{"class":121},[115,34572,34573],{"class":132}," \"strict-origin-when-cross-origin\"\n",[16,34575,34576],{},"Key points:",[63,34578,34579,34584,34589,34594,34599],{},[66,34580,34581,34583],{},[20,34582,32431],{}," prevents debug tracebacks and development behavior.",[66,34585,34586,34588],{},[20,34587,2719],{}," should list only your actual domains.",[66,34590,34591,34593],{},[20,34592,2713],{}," should come from runtime environment, not source control.",[66,34595,34596,34598],{},[20,34597,2725],{}," is required only for specific trusted origins, such as cross-origin POSTs or certain proxy or host setups; do not add entries unless needed.",[66,34600,34601,34603,34604,34606],{},[20,34602,2377],{}," should only be used if your reverse proxy sets ",[20,34605,3203],{}," correctly.",[16,34608,8572],{},[106,34610,34611],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,34612,34613],{"__ignoreMap":111},[115,34614,34615,34617,34619,34621],{"class":117,"line":118},[115,34616,1114],{"class":262},[115,34618,1117],{"class":132},[115,34620,1814],{"class":132},[115,34622,1817],{"class":202},[16,34624,34625,34626,1153,34628,34630],{},"Rollback note: bad ",[20,34627,2719],{},[20,34629,2725],{},", or proxy SSL settings can break requests and logins immediately. Keep the previous known-good settings file or release artifact available.",[11,34632,34634],{"id":34633},"_3-enforce-https-and-forward-the-right-headers","3) Enforce HTTPS and forward the right headers",[16,34636,34637],{},"In most Linux deployments, Nginx should handle HTTP-to-HTTPS redirects and proxy traffic to Gunicorn.",[16,34639,34640],{},"Example Nginx config:",[106,34642,34644],{"className":2154,"code":34643,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    add_header X-Content-Type-Options nosniff always;\n    add_header X-Frame-Options DENY always;\n    add_header Referrer-Policy strict-origin-when-cross-origin always;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fexample\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fexample\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,34645,34646,34652,34660,34666,34674,34678,34682,34688,34696,34702,34706,34712,34718,34722,34730,34737,34744,34748,34756,34763,34767,34771,34779,34786,34790,34794,34802,34808,34814,34820,34826,34832,34836],{"__ignoreMap":111},[115,34647,34648,34650],{"class":117,"line":118},[115,34649,2163],{"class":121},[115,34651,2166],{"class":125},[115,34653,34654,34656,34658],{"class":117,"line":136},[115,34655,2171],{"class":121},[115,34657,3808],{"class":202},[115,34659,3811],{"class":125},[115,34661,34662,34664],{"class":117,"line":149},[115,34663,2182],{"class":121},[115,34665,3713],{"class":125},[115,34667,34668,34670,34672],{"class":117,"line":162},[115,34669,3822],{"class":121},[115,34671,3825],{"class":202},[115,34673,3828],{"class":125},[115,34675,34676],{"class":117,"line":175},[115,34677,2323],{"class":125},[115,34679,34680],{"class":117,"line":350},[115,34681,310],{"emptyLinePlaceholder":309},[115,34683,34684,34686],{"class":117,"line":365},[115,34685,2163],{"class":121},[115,34687,2166],{"class":125},[115,34689,34690,34692,34694],{"class":117,"line":380},[115,34691,2171],{"class":121},[115,34693,2174],{"class":202},[115,34695,2177],{"class":125},[115,34697,34698,34700],{"class":117,"line":487},[115,34699,2182],{"class":121},[115,34701,3713],{"class":125},[115,34703,34704],{"class":117,"line":2095},[115,34705,310],{"emptyLinePlaceholder":309},[115,34707,34708,34710],{"class":117,"line":2104},[115,34709,2194],{"class":121},[115,34711,2197],{"class":125},[115,34713,34714,34716],{"class":117,"line":2113},[115,34715,2202],{"class":121},[115,34717,2205],{"class":125},[115,34719,34720],{"class":117,"line":2122},[115,34721,310],{"emptyLinePlaceholder":309},[115,34723,34724,34727],{"class":117,"line":2131},[115,34725,34726],{"class":121},"    add_header ",[115,34728,34729],{"class":125},"X-Content-Type-Options nosniff always;\n",[115,34731,34732,34734],{"class":117,"line":2136},[115,34733,34726],{"class":121},[115,34735,34736],{"class":125},"X-Frame-Options DENY always;\n",[115,34738,34739,34741],{"class":117,"line":2142},[115,34740,34726],{"class":121},[115,34742,34743],{"class":125},"Referrer-Policy strict-origin-when-cross-origin always;\n",[115,34745,34746],{"class":117,"line":2273},[115,34747,310],{"emptyLinePlaceholder":309},[115,34749,34750,34752,34754],{"class":117,"line":2282},[115,34751,2214],{"class":121},[115,34753,2217],{"class":262},[115,34755,2220],{"class":125},[115,34757,34758,34760],{"class":117,"line":2291},[115,34759,2225],{"class":121},[115,34761,34762],{"class":125},"\u002Fsrv\u002Fexample\u002Fstatic\u002F;\n",[115,34764,34765],{"class":117,"line":2299},[115,34766,2233],{"class":125},[115,34768,34769],{"class":117,"line":2307},[115,34770,310],{"emptyLinePlaceholder":309},[115,34772,34773,34775,34777],{"class":117,"line":2315},[115,34774,2214],{"class":121},[115,34776,2244],{"class":262},[115,34778,2220],{"class":125},[115,34780,34781,34783],{"class":117,"line":2320},[115,34782,2225],{"class":121},[115,34784,34785],{"class":125},"\u002Fsrv\u002Fexample\u002Fmedia\u002F;\n",[115,34787,34788],{"class":117,"line":7083},[115,34789,2233],{"class":125},[115,34791,34792],{"class":117,"line":7090},[115,34793,310],{"emptyLinePlaceholder":309},[115,34795,34796,34798,34800],{"class":117,"line":7097},[115,34797,2214],{"class":121},[115,34799,2268],{"class":262},[115,34801,2220],{"class":125},[115,34803,34804,34806],{"class":117,"line":7108},[115,34805,2276],{"class":121},[115,34807,3748],{"class":125},[115,34809,34810,34812],{"class":117,"line":7113},[115,34811,2285],{"class":121},[115,34813,2288],{"class":125},[115,34815,34816,34818],{"class":117,"line":16535},[115,34817,2285],{"class":121},[115,34819,3767],{"class":125},[115,34821,34822,34824],{"class":117,"line":16544},[115,34823,2285],{"class":121},[115,34825,2312],{"class":125},[115,34827,34828,34830],{"class":117,"line":16549},[115,34829,2285],{"class":121},[115,34831,2304],{"class":125},[115,34833,34834],{"class":117,"line":16555},[115,34835,2233],{"class":125},[115,34837,34838],{"class":117,"line":16564},[115,34839,2323],{"class":125},[16,34841,8572],{},[106,34843,34844],{"className":108,"code":13228,"language":110,"meta":111,"style":111},[20,34845,34846,34854],{"__ignoreMap":111},[115,34847,34848,34850,34852],{"class":117,"line":118},[115,34849,2764],{"class":262},[115,34851,2767],{"class":202},[115,34853,6494],{"class":132},[115,34855,34856,34858,34860],{"class":117,"line":136},[115,34857,2764],{"class":262},[115,34859,2767],{"class":202},[115,34861,2770],{"class":132},[16,34863,34864],{},"You should see an HTTP redirect on port 80 and a valid HTTPS response on 443.",[16,34866,34867],{},"HSTS should be added carefully. Start with a low value like:",[106,34869,34870],{"className":2369,"code":8131,"language":1114,"meta":111,"style":111},[20,34871,34872],{"__ignoreMap":111},[115,34873,34874,34876,34878],{"class":117,"line":118},[115,34875,7440],{"class":202},[115,34877,2380],{"class":121},[115,34879,7445],{"class":202},[16,34881,34882,34883,4493,34885,34887],{},"After confirming HTTPS is stable, increase it gradually. Do not set ",[20,34884,7464],{},[20,34886,12004],{}," until you are sure every covered hostname is ready. HSTS mistakes are hard to roll back for users who already cached the policy.",[11,34889,34891],{"id":34890},"_4-protect-secrets-and-environment-configuration","4) Protect secrets and environment configuration",[16,34893,34894],{},"Do not store these in the repo:",[63,34896,34897,34902,34905,34907],{},[66,34898,34899],{},[20,34900,34901],{},"DJANGO_SECRET_KEY",[66,34903,34904],{},"database password",[66,34906,32639],{},[66,34908,34909],{},"third-party API tokens",[16,34911,34912],{},"A common non-container approach is a restricted environment file consumed by systemd.",[16,34914,12414],{},[106,34916,34918],{"className":2026,"code":34917,"language":2028,"meta":111,"style":111},"# \u002Fetc\u002Fexample\u002Fexample.env\nDJANGO_SECRET_KEY=replace-with-real-secret\nDB_NAME=appdb\nDB_USER=appuser\nDB_PASSWORD=strongpassword\nDB_HOST=127.0.0.1\nDB_PORT=5432\n",[20,34919,34920,34925,34932,34940,34948,34956,34964],{"__ignoreMap":111},[115,34921,34922],{"class":117,"line":118},[115,34923,34924],{"class":3861},"# \u002Fetc\u002Fexample\u002Fexample.env\n",[115,34926,34927,34929],{"class":117,"line":136},[115,34928,34901],{"class":121},[115,34930,34931],{"class":125},"=replace-with-real-secret\n",[115,34933,34934,34937],{"class":117,"line":149},[115,34935,34936],{"class":121},"DB_NAME",[115,34938,34939],{"class":125},"=appdb\n",[115,34941,34942,34945],{"class":117,"line":162},[115,34943,34944],{"class":121},"DB_USER",[115,34946,34947],{"class":125},"=appuser\n",[115,34949,34950,34953],{"class":117,"line":175},[115,34951,34952],{"class":121},"DB_PASSWORD",[115,34954,34955],{"class":125},"=strongpassword\n",[115,34957,34958,34961],{"class":117,"line":350},[115,34959,34960],{"class":121},"DB_HOST",[115,34962,34963],{"class":125},"=127.0.0.1\n",[115,34965,34966,34969],{"class":117,"line":365},[115,34967,34968],{"class":121},"DB_PORT",[115,34970,34971],{"class":125},"=5432\n",[16,34973,34974],{},"Permissions:",[106,34976,34978],{"className":108,"code":34977,"language":110,"meta":111,"style":111},"sudo chown root:example \u002Fetc\u002Fexample\u002Fexample.env\nsudo chmod 640 \u002Fetc\u002Fexample\u002Fexample.env\n",[20,34979,34980,34992],{"__ignoreMap":111},[115,34981,34982,34984,34986,34989],{"class":117,"line":118},[115,34983,2001],{"class":262},[115,34985,6733],{"class":132},[115,34987,34988],{"class":132}," root:example",[115,34990,34991],{"class":132}," \u002Fetc\u002Fexample\u002Fexample.env\n",[115,34993,34994,34996,34998,35000],{"class":117,"line":136},[115,34995,2001],{"class":262},[115,34997,12480],{"class":132},[115,34999,12483],{"class":202},[115,35001,34991],{"class":132},[16,35003,35004],{},"Treat this env file as sensitive operational data: restrict group membership, exclude it from source control, and secure any backups that include it.",[16,35006,35007],{},"Systemd service example:",[106,35009,35011],{"className":2026,"code":35010,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for example Django app\nAfter=network.target\n\n[Service]\nUser=example\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fexample\u002Fapp\nEnvironmentFile=\u002Fetc\u002Fexample\u002Fexample.env\nExecStart=\u002Fsrv\u002Fexample\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n",[20,35012,35013,35017,35024,35030,35034,35038,35045,35051,35058,35065,35072,35078,35082,35086],{"__ignoreMap":111},[115,35014,35015],{"class":117,"line":118},[115,35016,2035],{"class":262},[115,35018,35019,35021],{"class":117,"line":136},[115,35020,2040],{"class":121},[115,35022,35023],{"class":125},"=Gunicorn for example Django app\n",[115,35025,35026,35028],{"class":117,"line":149},[115,35027,2048],{"class":121},[115,35029,2051],{"class":125},[115,35031,35032],{"class":117,"line":162},[115,35033,310],{"emptyLinePlaceholder":309},[115,35035,35036],{"class":117,"line":175},[115,35037,2060],{"class":262},[115,35039,35040,35042],{"class":117,"line":350},[115,35041,2065],{"class":121},[115,35043,35044],{"class":125},"=example\n",[115,35046,35047,35049],{"class":117,"line":365},[115,35048,2073],{"class":121},[115,35050,2076],{"class":125},[115,35052,35053,35055],{"class":117,"line":380},[115,35054,2081],{"class":121},[115,35056,35057],{"class":125},"=\u002Fsrv\u002Fexample\u002Fapp\n",[115,35059,35060,35062],{"class":117,"line":487},[115,35061,2089],{"class":121},[115,35063,35064],{"class":125},"=\u002Fetc\u002Fexample\u002Fexample.env\n",[115,35066,35067,35069],{"class":117,"line":2095},[115,35068,2107],{"class":121},[115,35070,35071],{"class":125},"=\u002Fsrv\u002Fexample\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000\n",[115,35073,35074,35076],{"class":117,"line":2104},[115,35075,2116],{"class":121},[115,35077,4932],{"class":125},[115,35079,35080],{"class":117,"line":2113},[115,35081,310],{"emptyLinePlaceholder":309},[115,35083,35084],{"class":117,"line":2122},[115,35085,2139],{"class":262},[115,35087,35088,35090],{"class":117,"line":2131},[115,35089,2145],{"class":121},[115,35091,2148],{"class":125},[16,35093,35094],{},"Use least privilege everywhere:",[63,35096,35097,35100,35103],{},[66,35098,35099],{},"database user should only have access to its own database",[66,35101,35102],{},"Redis should not be publicly exposed",[66,35104,35105],{},"app services should not run as root",[16,35107,8572],{},[106,35109,35110],{"className":108,"code":31429,"language":110,"meta":111,"style":111},[20,35111,35112,35120,35130],{"__ignoreMap":111},[115,35113,35114,35116,35118],{"class":117,"line":118},[115,35115,2001],{"class":262},[115,35117,3480],{"class":132},[115,35119,4984],{"class":132},[115,35121,35122,35124,35126,35128],{"class":117,"line":136},[115,35123,2001],{"class":262},[115,35125,3480],{"class":132},[115,35127,3483],{"class":132},[115,35129,1987],{"class":132},[115,35131,35132,35134,35136,35138],{"class":117,"line":149},[115,35133,2001],{"class":262},[115,35135,3480],{"class":132},[115,35137,1984],{"class":132},[115,35139,1987],{"class":132},[11,35141,35143],{"id":35142},"_5-reduce-server-attack-surface","5) Reduce server attack surface",[16,35145,35146],{},"Do not expose Gunicorn directly to the internet. Bind it to localhost or a Unix socket.",[16,35148,23846],{},[106,35150,35152],{"className":108,"code":35151,"language":110,"meta":111,"style":111},"ss -tulpn\n",[20,35153,35154],{"__ignoreMap":111},[115,35155,35156,35158],{"class":117,"line":118},[115,35157,6472],{"class":262},[115,35159,2007],{"class":202},[16,35161,35162],{},"You should not see Gunicorn listening on a public IP.",[16,35164,35165],{},"Basic UFW example:",[106,35167,35168],{"className":108,"code":29163,"language":110,"meta":111,"style":111},[20,35169,35170,35180,35190,35198],{"__ignoreMap":111},[115,35171,35172,35174,35176,35178],{"class":117,"line":118},[115,35173,2001],{"class":262},[115,35175,2014],{"class":132},[115,35177,14341],{"class":132},[115,35179,14344],{"class":132},[115,35181,35182,35184,35186,35188],{"class":117,"line":136},[115,35183,2001],{"class":262},[115,35185,2014],{"class":132},[115,35187,14341],{"class":132},[115,35189,29186],{"class":132},[115,35191,35192,35194,35196],{"class":117,"line":149},[115,35193,2001],{"class":262},[115,35195,2014],{"class":132},[115,35197,14375],{"class":132},[115,35199,35200,35202,35204],{"class":117,"line":162},[115,35201,2001],{"class":262},[115,35203,2014],{"class":132},[115,35205,2017],{"class":132},[16,35207,35208],{},"That typically allows only SSH, HTTP, and HTTPS. Your database should not be public unless there is a deliberate reason and additional controls.",[16,35210,35211,35212,241],{},"SSH hardening basics in ",[20,35213,14384],{},[106,35215,35218],{"className":35216,"code":35217,"language":247,"meta":111},[245],"PasswordAuthentication no\nPermitRootLogin no\nPubkeyAuthentication yes\n",[20,35219,35217],{"__ignoreMap":111},[16,35221,35222],{},"After changes:",[106,35224,35226],{"className":108,"code":35225,"language":110,"meta":111,"style":111},"sudo systemctl restart ssh\n",[20,35227,35228],{"__ignoreMap":111},[115,35229,35230,35232,35234,35236],{"class":117,"line":118},[115,35231,2001],{"class":262},[115,35233,3480],{"class":132},[115,35235,3483],{"class":132},[115,35237,14418],{"class":132},[16,35239,35240],{},"Be careful: test a second SSH session before closing the first one, or you can lock yourself out.",[11,35242,35244],{"id":35243},"_6-secure-static-files-media-and-admin-exposure","6) Secure static files, media, and admin exposure",[16,35246,35247],{},"Do not rely on Django debug static serving in production. Collect static files into a known path and let Nginx serve them:",[106,35249,35250],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,35251,35252],{"__ignoreMap":111},[115,35253,35254,35256,35258,35260],{"class":117,"line":118},[115,35255,1114],{"class":262},[115,35257,1117],{"class":132},[115,35259,1838],{"class":132},[115,35261,1841],{"class":202},[16,35263,35264],{},"Treat user-uploaded media as untrusted:",[63,35266,35267,35270,35273,35276,35279],{},[66,35268,35269],{},"validate file types in application logic",[66,35271,35272],{},"store uploads outside code directories",[66,35274,35275],{},"do not allow execution from upload paths",[66,35277,35278],{},"keep media and static paths separate",[66,35280,35281],{},"if users can upload files, configure the web server so upload directories are served as data only and not treated as executable content",[16,35283,35284],{},"Admin exposure should be reduced where possible:",[63,35286,35287,35290,35293,35296],{},[66,35288,35289],{},"strong unique passwords",[66,35291,35292],{},"staff-only accounts",[66,35294,35295],{},"no shared admin users",[66,35297,35298],{},"consider IP restriction or VPN access for sensitive environments",[11,35300,35302],{"id":35301},"_7-keep-the-stack-patched-and-reproducible","7) Keep the stack patched and reproducible",[16,35304,35305],{},"Update Django and Python dependencies regularly. Pin versions so deploys are repeatable.",[16,35307,35308],{},"Dependency review basics:",[106,35310,35312],{"className":108,"code":35311,"language":110,"meta":111,"style":111},"pip list --outdated\n",[20,35313,35314],{"__ignoreMap":111},[115,35315,35316,35318,35320],{"class":117,"line":118},[115,35317,8618],{"class":262},[115,35319,19138],{"class":132},[115,35321,35322],{"class":202}," --outdated\n",[16,35324,35325],{},"On Debian or Ubuntu systems:",[106,35327,35329],{"className":108,"code":35328,"language":110,"meta":111,"style":111},"sudo apt update\nsudo apt upgrade\n",[20,35330,35331,35339],{"__ignoreMap":111},[115,35332,35333,35335,35337],{"class":117,"line":118},[115,35334,2001],{"class":262},[115,35336,6588],{"class":132},[115,35338,6591],{"class":132},[115,35340,35341,35343,35345],{"class":117,"line":136},[115,35342,2001],{"class":262},[115,35344,6588],{"class":132},[115,35346,35347],{"class":132}," upgrade\n",[16,35349,35350],{},"Keep Nginx, OpenSSL, Python runtime, and database packages current. Review changelogs before deploying major updates.",[16,35352,35353],{},"Avoid one-off production edits that never make it back into versioned config. Security drift usually starts there.",[52,35355,35357],{"id":35356},"when-manual-security-setup-becomes-repetitive","When manual security setup becomes repetitive",[16,35359,35360,35361,1153,35363,35365],{},"If you keep repeating the same Django settings hardening, Nginx headers, systemd units, firewall rules, and post-deploy checks across multiple apps, standardize them. Good first candidates are a hardened Django production settings module, reverse proxy config templates, systemd service files, and a small validation script that runs ",[20,35362,20349],{},[20,35364,2764],{},", and port checks after each release.",[11,35367,35369],{"id":35368},"_8-log-enough-to-investigate-without-leaking-secrets","8) Log enough to investigate without leaking secrets",[16,35371,35372],{},"At minimum, keep:",[63,35374,35375,35378,35381,35384],{},[66,35376,35377],{},"Django application error logs",[66,35379,35380],{},"Nginx access and error logs",[66,35382,35383],{},"authentication failure visibility",[66,35385,35386],{},"deploy event logs",[16,35388,35389],{},"Do not log:",[63,35391,35392,35395,35398,35401],{},[66,35393,35394],{},"environment variable dumps",[66,35396,35397],{},"raw secrets or tokens",[66,35399,35400],{},"full request bodies containing credentials",[66,35402,35403],{},"verbose debug tracebacks in user-facing responses",[16,35405,35406],{},"Add basic monitoring:",[63,35408,35409,35412,35415],{},[66,35410,35411],{},"uptime or health check endpoint",[66,35413,35414],{},"alerting for 5xx spikes",[66,35416,35417],{},"certificate expiry checks if possible",[11,35419,1321],{"id":1320},[16,35421,35422],{},"This setup works because it splits responsibilities cleanly:",[63,35424,35425,35430,35435,35440],{},[66,35426,35427,35429],{},[1226,35428,20302],{}," handles application-level security settings, cookies, host validation, CSRF, and error behavior.",[66,35431,35432,35434],{},[1226,35433,1647],{}," handles public network exposure, TLS termination, redirects, static and media delivery, and forwarding trusted proxy headers.",[66,35436,35437,35439],{},[1226,35438,1277],{}," runs the app with a non-root user and injects runtime secrets without committing them to source control.",[66,35441,35442,35445],{},[1226,35443,35444],{},"firewall and SSH config"," reduce how much of the server is reachable at all.",[16,35447,35448],{},"If you are using Docker, the same principles still apply. The exact file locations change, but the controls do not: no public app server port unless required, TLS handled deliberately, secrets injected at runtime, non-root execution where practical, and only required ports published.",[11,35450,30532],{"id":30531},[63,35452,35453,35464,35473,35479,35485,35494],{},[66,35454,35455,35460,35461,35463],{},[1226,35456,35457,35459],{},[20,35458,2407],{}," vs Nginx redirect:"," If Nginx already redirects HTTP to HTTPS, keep the redirect there. That is usually simpler. But if Django can ever be reached directly, disabling ",[20,35462,2407],{}," removes an extra safety layer.",[66,35465,35466,35469,35470,35472],{},[1226,35467,35468],{},"Proxy SSL header trust:"," Only set ",[20,35471,2377],{}," when your proxy is under your control and always sends the expected header.",[66,35474,35475,35478],{},[1226,35476,35477],{},"HSTS rollout:"," Start low. Bad HSTS settings can lock clients into HTTPS before your full setup is ready.",[66,35480,35481,35484],{},[1226,35482,35483],{},"Login or session issues:"," Overly strict cookie settings or wrong forwarded-proto handling can cause session loops or CSRF failures.",[66,35486,35487,35490,35491,35493],{},[1226,35488,35489],{},"Migrations before go-live:"," Security settings are not enough if the release itself is inconsistent. Run migrations in a predictable deploy sequence. Some schema migrations are not cleanly reversible. Take a verified backup before running production migrations, and do not assume rollback means ",[20,35492,10296],{}," can always undo the change.",[66,35495,35496,35499],{},[1226,35497,35498],{},"Backups:"," Database dumps and env files are sensitive. Encrypt backups if possible and test restore steps, not just backup creation.",[16,35501,35502],{},"Minimum rollback plan:",[1173,35504,35505,35508,35511,35514],{},[66,35506,35507],{},"keep the previous Django settings and Nginx config",[66,35509,35510],{},"revert the release artifact or config",[66,35512,35513],{},"restart Gunicorn and Nginx",[66,35515,35516],{},"re-run health and header checks",[11,35518,1386],{"id":1385},[16,35520,35521,35522,211],{},"For a broader baseline, see the ",[1395,35523,35524],{"href":3006},"Django production settings checklist",[16,35526,35527,35528,211],{},"If you need the full app server and proxy path, follow ",[1395,35529,35530],{"href":2985},"how to deploy Django with Gunicorn and Nginx",[16,35532,35533,35534,211],{},"For TLS-specific setup, see ",[1395,35535,35536],{"href":4428},"how to configure HTTPS for Django behind Nginx",[16,35538,35539,35540,211],{},"Before launch, use a final ",[1395,35541,32365],{"href":2999},[11,35543,1420],{"id":1419},[52,35545,35547],{"id":35546},"what-are-the-most-important-django-security-settings-for-production","What are the most important Django security settings for production?",[16,35549,34066,35550,34069,35552,1153,35554,35556],{},[20,35551,2707],{},[20,35553,2719],{},[20,35555,2713],{}," from environment or secret storage, secure cookie flags, correct proxy SSL handling, and deliberate HSTS configuration.",[52,35558,35560],{"id":35559},"should-django-handle-https-redirects-or-should-nginx-handle-them","Should Django handle HTTPS redirects or should Nginx handle them?",[16,35562,35563],{},"Usually Nginx should handle them. It keeps transport concerns at the proxy layer and avoids redirect confusion when Django is behind a reverse proxy.",[52,35565,35567,35568,35570],{"id":35566},"is-python-managepy-check-deploy-enough-to-secure-a-django-deployment","Is ",[20,35569,15970],{}," enough to secure a Django deployment?",[16,35572,35573],{},"No. It is a useful baseline check, but it does not verify firewall rules, public port exposure, SSH hardening, patch levels, backup security, or whether your reverse proxy is configured safely.",[52,35575,35577,35578,35580],{"id":35576},"how-should-i-store-secret_key-and-database-credentials-in-production","How should I store ",[20,35579,2713],{}," and database credentials in production?",[16,35582,35583],{},"Load them from environment variables or a secret store at runtime. If you use an env file, keep it outside the repo with restricted ownership and permissions, and inject it through systemd or your container platform.",[52,35585,35587],{"id":35586},"what-is-the-safest-way-to-add-hsts-without-breaking-access","What is the safest way to add HSTS without breaking access?",[16,35589,35590,35591,3146,35593,35595],{},"Start with a small value like 300 seconds, verify HTTPS is stable across the site, then increase gradually. Delay ",[20,35592,18711],{},[20,35594,18714],{}," until you are certain every covered hostname supports HTTPS correctly.",[1485,35597,4517],{},{"title":111,"searchDepth":149,"depth":149,"links":35599},[35600,35601,35602,35603,35604,35605,35606,35607,35608,35609,35612,35613,35614,35615,35616],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":34264,"depth":136,"text":34265},{"id":34297,"depth":136,"text":34298},{"id":34633,"depth":136,"text":34634},{"id":34890,"depth":136,"text":34891},{"id":35142,"depth":136,"text":35143},{"id":35243,"depth":136,"text":35244},{"id":35301,"depth":136,"text":35302,"children":35610},[35611],{"id":35356,"depth":149,"text":35357},{"id":35368,"depth":136,"text":35369},{"id":1320,"depth":136,"text":1321},{"id":30531,"depth":136,"text":30532},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":35617},[35618,35619,35620,35622,35624],{"id":35546,"depth":149,"text":35547},{"id":35559,"depth":149,"text":35560},{"id":35566,"depth":149,"text":35621},"Is python manage.py check --deploy enough to secure a Django deployment?",{"id":35576,"depth":149,"text":35623},"How should I store SECRET_KEY and database credentials in production?",{"id":35586,"depth":149,"text":35587},"A lot of Django apps reach production with local-development assumptions still in place: DEBUG=True, weak host validation, secrets in .env files with broad permissions, Gunicorn...",{},"\u002Fdjango-deployment-security-basics","2",[1409,35630,35631],"\u002Foptimize\u002Fdjango-health-check-endpoint-guide","\u002Foptimize\u002Fdjango-logging-setup-production",{"title":34171,"description":35625},[1557],"django-deployment-security-basics",[1557],"sRFqCbqaeZnNKiuxoJ3ZW5xzxu3pKixSjtiBr2pHw8Q",{"id":35638,"title":35639,"body":35640,"category":1541,"description":35646,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":37873,"navigation":309,"path":37874,"priority":4549,"related":37875,"role":34162,"section":1554,"seo":37878,"stack":37879,"stem":37880,"tags":37881,"type":3104,"__hash__":37882},"articles\u002Fdjango-logging-setup-production.md","Django Logging Setup for Production",{"type":8,"value":35641,"toc":37835},[35642,35644,35647,35650,35653,35656,35658,35665,35716,35718,35722,35725,35729,35732,35745,35749,35751,35765,35769,35771,35782,35785,35787,35791,35797,36402,36405,36426,36431,36436,36439,36457,36460,36483,36486,36501,36504,36506,36510,36513,36516,36574,36581,36584,36694,36697,36742,36746,36749,36769,36772,36776,36782,36811,36814,36852,36854,36858,36861,36864,36902,36908,36957,36960,36969,36972,36989,37003,37006,37018,37022,37025,37028,37041,37045,37048,37063,37066,37068,37072,37075,37078,37095,37097,37117,37120,37293,37296,37409,37416,37418,37422,37425,37429,37443,37447,37461,37465,37479,37482,37540,37543,37545,37549,37552,37556,37574,37578,37581,37584,37627,37630,37637,37641,37661,37665,37668,37684,37686,37693,37696,37699,37709,37711,37734,37738,37747,37749,37754,37762,37767,37773,37775,37779,37782,37786,37801,37805,37808,37812,37815,37819,37832],[11,35643,14],{"id":13},[16,35645,35646],{},"A default Django project does not give you a production-safe logging setup.",[16,35648,35649],{},"In real deployments, you need logs that help during incidents: application errors, unexpected warnings, failed requests, and server-side failures must be visible and preserved long enough to investigate. At the same time, debug-style logging in production can create noise, consume disk, and expose sensitive data such as tokens, cookies, or request payloads.",[16,35651,35652],{},"A useful production setup must also fit the rest of the stack. Django logs are only one part of the picture. Gunicorn or Uvicorn will produce process and worker logs, and Nginx or Caddy will produce access and proxy error logs. If these are mixed together or routed inconsistently, troubleshooting becomes slower.",[16,35654,35655],{},"The goal is a Django logging configuration production teams can actually operate: predictable destinations, useful formatting, controlled log levels, and safe handling of sensitive data.",[11,35657,30],{"id":29},[16,35659,35660,35661,35664],{},"For a practical ",[1226,35662,35663],{},"Django logging setup for production"," baseline:",[63,35666,35667,35672,35680,35687,35707,35710,35713],{},[66,35668,35669,35670],{},"run with ",[20,35671,2707],{},[66,35673,35674,35675,4493,35677],{},"log Django application events at ",[20,35676,5173],{},[20,35678,35679],{},"WARNING",[66,35681,35682,35683,35686],{},"make ",[20,35684,35685],{},"ERROR"," and 5xx events easy to find",[66,35688,35689,35690],{},"send logs to:\n",[63,35691,35692,35698,35704],{},[66,35693,35694,35697],{},[20,35695,35696],{},"stdout\u002Fstderr"," in Docker or supervised environments",[66,35699,35700,35703],{},[20,35701,35702],{},"journald"," with systemd on Linux servers",[66,35705,35706],{},"files only if you also configure rotation and permissions",[66,35708,35709],{},"keep Django logs separate from Gunicorn access\u002Ferror logs and Nginx logs",[66,35711,35712],{},"do not log secrets, tokens, cookies, passwords, or full request bodies",[66,35714,35715],{},"verify the setup by emitting a test log and forcing a controlled exception",[11,35717,43],{"id":42},[52,35719,35721],{"id":35720},"_1-choose-a-log-destination-first","1. Choose a log destination first",[16,35723,35724],{},"Pick one destination model before editing Django settings.",[1850,35726,35728],{"id":35727},"use-stdoutstderr","Use stdout\u002Fstderr",[16,35730,35731],{},"Best for:",[63,35733,35734,35737,35739,35742],{},[66,35735,35736],{},"Docker",[66,35738,1675],{},[66,35740,35741],{},"platform logging collectors",[66,35743,35744],{},"systemd-supervised processes when journald captures service output",[1850,35746,35748],{"id":35747},"use-files","Use files",[16,35750,35731],{},[63,35752,35753,35756,35762],{},[66,35754,35755],{},"simple single-server deployments",[66,35757,35758,35759],{},"teams already using ",[20,35760,35761],{},"\u002Fvar\u002Flog\u002F...",[66,35763,35764],{},"environments where rotation and retention are managed locally",[1850,35766,35768],{"id":35767},"use-journald-or-centralized-logging","Use journald or centralized logging",[16,35770,35731],{},[63,35772,35773,35776,35779],{},[66,35774,35775],{},"systemd-based Linux servers",[66,35777,35778],{},"searchable service logs",[66,35780,35781],{},"correlation across app, worker, and proxy services",[16,35783,35784],{},"If you are on a VM with Gunicorn managed by systemd, journald is usually simpler than app-managed log files.",[23099,35786],{},[52,35788,35790],{"id":35789},"_2-set-production-safe-django-logging","2. Set production-safe Django logging",[16,35792,35793,35794,35796],{},"In your production settings module, define ",[20,35795,3337],{}," explicitly.",[106,35798,35800],{"className":2369,"code":35799,"language":1114,"meta":111,"style":111},"import os\nimport logging.handlers\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\nDEBUG = False\n\nLOG_TO_FILE = os.getenv(\"DJANGO_LOG_TO_FILE\", \"0\") == \"1\"\nLOG_LEVEL = os.getenv(\"DJANGO_LOG_LEVEL\", \"INFO\")\nLOG_DIR = os.getenv(\"DJANGO_LOG_DIR\", \"\u002Fvar\u002Flog\u002Fmyapp\")\n\nif LOG_TO_FILE:\n    os.makedirs(LOG_DIR, exist_ok=True)\n\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"standard\": {\n            \"format\": \"%(asctime)s %(levelname)s %(name)s %(process)d %(message)s\",\n        },\n    },\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"standard\",\n        },\n        \"file\": {\n            \"class\": \"logging.handlers.WatchedFileHandler\",\n            \"filename\": f\"{LOG_DIR}\u002Fdjango.log\",\n            \"formatter\": \"standard\",\n        },\n    },\n    \"root\": {\n        \"handlers\": [\"file\"] if LOG_TO_FILE else [\"console\"],\n        \"level\": LOG_LEVEL,\n    },\n    \"loggers\": {\n        \"django\": {\n            \"handlers\": [\"file\"] if LOG_TO_FILE else [\"console\"],\n            \"level\": \"INFO\",\n            \"propagate\": False,\n        },\n        \"django.request\": {\n            \"handlers\": [\"file\"] if LOG_TO_FILE else [\"console\"],\n            \"level\": \"ERROR\",\n            \"propagate\": False,\n        },\n        \"django.server\": {\n            \"handlers\": [\"file\"] if LOG_TO_FILE else [\"console\"],\n            \"level\": \"WARNING\",\n            \"propagate\": False,\n        },\n        \"myapp\": {\n            \"handlers\": [\"file\"] if LOG_TO_FILE else [\"console\"],\n            \"level\": LOG_LEVEL,\n            \"propagate\": False,\n        },\n    },\n}\n",[20,35801,35802,35808,35815,35825,35829,35841,35845,35853,35857,35882,35901,35920,35924,35933,35952,35956,35964,35974,35984,35991,35998,36026,36030,36034,36040,36046,36056,36068,36072,36079,36090,36110,36120,36124,36128,36135,36160,36171,36175,36181,36188,36210,36220,36230,36234,36241,36263,36274,36284,36288,36295,36317,36327,36337,36341,36348,36370,36380,36390,36394,36398],{"__ignoreMap":111},[115,35803,35804,35806],{"class":117,"line":118},[115,35805,5613],{"class":121},[115,35807,5616],{"class":125},[115,35809,35810,35812],{"class":117,"line":136},[115,35811,5613],{"class":121},[115,35813,35814],{"class":125}," logging.handlers\n",[115,35816,35817,35819,35821,35823],{"class":117,"line":149},[115,35818,5621],{"class":121},[115,35820,16353],{"class":125},[115,35822,5613],{"class":121},[115,35824,16358],{"class":125},[115,35826,35827],{"class":117,"line":162},[115,35828,310],{"emptyLinePlaceholder":309},[115,35830,35831,35833,35835,35837,35839],{"class":117,"line":175},[115,35832,16374],{"class":202},[115,35834,2380],{"class":121},[115,35836,16379],{"class":125},[115,35838,16382],{"class":202},[115,35840,16385],{"class":125},[115,35842,35843],{"class":117,"line":350},[115,35844,310],{"emptyLinePlaceholder":309},[115,35846,35847,35849,35851],{"class":117,"line":365},[115,35848,7350],{"class":202},[115,35850,2380],{"class":121},[115,35852,7355],{"class":202},[115,35854,35855],{"class":117,"line":380},[115,35856,310],{"emptyLinePlaceholder":309},[115,35858,35859,35862,35864,35867,35870,35872,35875,35877,35879],{"class":117,"line":487},[115,35860,35861],{"class":202},"LOG_TO_FILE",[115,35863,2380],{"class":121},[115,35865,35866],{"class":125}," os.getenv(",[115,35868,35869],{"class":132},"\"DJANGO_LOG_TO_FILE\"",[115,35871,1153],{"class":125},[115,35873,35874],{"class":132},"\"0\"",[115,35876,18281],{"class":125},[115,35878,25175],{"class":121},[115,35880,35881],{"class":132}," \"1\"\n",[115,35883,35884,35887,35889,35891,35894,35896,35899],{"class":117,"line":2095},[115,35885,35886],{"class":202},"LOG_LEVEL",[115,35888,2380],{"class":121},[115,35890,35866],{"class":125},[115,35892,35893],{"class":132},"\"DJANGO_LOG_LEVEL\"",[115,35895,1153],{"class":125},[115,35897,35898],{"class":132},"\"INFO\"",[115,35900,2394],{"class":125},[115,35902,35903,35906,35908,35910,35913,35915,35918],{"class":117,"line":2104},[115,35904,35905],{"class":202},"LOG_DIR",[115,35907,2380],{"class":121},[115,35909,35866],{"class":125},[115,35911,35912],{"class":132},"\"DJANGO_LOG_DIR\"",[115,35914,1153],{"class":125},[115,35916,35917],{"class":132},"\"\u002Fvar\u002Flog\u002Fmyapp\"",[115,35919,2394],{"class":125},[115,35921,35922],{"class":117,"line":2113},[115,35923,310],{"emptyLinePlaceholder":309},[115,35925,35926,35928,35931],{"class":117,"line":2122},[115,35927,10833],{"class":121},[115,35929,35930],{"class":202}," LOG_TO_FILE",[115,35932,2498],{"class":125},[115,35934,35935,35938,35940,35942,35945,35947,35950],{"class":117,"line":2131},[115,35936,35937],{"class":125},"    os.makedirs(",[115,35939,35905],{"class":202},[115,35941,1153],{"class":125},[115,35943,35944],{"class":5680},"exist_ok",[115,35946,129],{"class":121},[115,35948,35949],{"class":202},"True",[115,35951,2394],{"class":125},[115,35953,35954],{"class":117,"line":2136},[115,35955,310],{"emptyLinePlaceholder":309},[115,35957,35958,35960,35962],{"class":117,"line":2142},[115,35959,3337],{"class":202},[115,35961,2380],{"class":121},[115,35963,2166],{"class":125},[115,35965,35966,35968,35970,35972],{"class":117,"line":2273},[115,35967,3346],{"class":132},[115,35969,2513],{"class":125},[115,35971,3351],{"class":202},[115,35973,3354],{"class":125},[115,35975,35976,35978,35980,35982],{"class":117,"line":2282},[115,35977,3359],{"class":132},[115,35979,2513],{"class":125},[115,35981,3364],{"class":202},[115,35983,3354],{"class":125},[115,35985,35986,35989],{"class":117,"line":2291},[115,35987,35988],{"class":132},"    \"formatters\"",[115,35990,3374],{"class":125},[115,35992,35993,35996],{"class":117,"line":2299},[115,35994,35995],{"class":132},"        \"standard\"",[115,35997,3374],{"class":125},[115,35999,36000,36003,36005,36007,36010,36013,36016,36019,36022,36024],{"class":117,"line":2307},[115,36001,36002],{"class":132},"            \"format\"",[115,36004,2513],{"class":125},[115,36006,331],{"class":132},[115,36008,36009],{"class":202},"%(asctime)s",[115,36011,36012],{"class":202}," %(levelname)s",[115,36014,36015],{"class":202}," %(name)s",[115,36017,36018],{"class":202}," %(process)d",[115,36020,36021],{"class":202}," %(message)s",[115,36023,331],{"class":132},[115,36025,3354],{"class":125},[115,36027,36028],{"class":117,"line":2315},[115,36029,3398],{"class":125},[115,36031,36032],{"class":117,"line":2320},[115,36033,3403],{"class":125},[115,36035,36036,36038],{"class":117,"line":7083},[115,36037,3371],{"class":132},[115,36039,3374],{"class":125},[115,36041,36042,36044],{"class":117,"line":7090},[115,36043,3379],{"class":132},[115,36045,3374],{"class":125},[115,36047,36048,36050,36052,36054],{"class":117,"line":7097},[115,36049,3386],{"class":132},[115,36051,2513],{"class":125},[115,36053,3391],{"class":132},[115,36055,3354],{"class":125},[115,36057,36058,36061,36063,36066],{"class":117,"line":7108},[115,36059,36060],{"class":132},"            \"formatter\"",[115,36062,2513],{"class":125},[115,36064,36065],{"class":132},"\"standard\"",[115,36067,3354],{"class":125},[115,36069,36070],{"class":117,"line":7113},[115,36071,3398],{"class":125},[115,36073,36074,36077],{"class":117,"line":16535},[115,36075,36076],{"class":132},"        \"file\"",[115,36078,3374],{"class":125},[115,36080,36081,36083,36085,36088],{"class":117,"line":16544},[115,36082,3386],{"class":132},[115,36084,2513],{"class":125},[115,36086,36087],{"class":132},"\"logging.handlers.WatchedFileHandler\"",[115,36089,3354],{"class":125},[115,36091,36092,36095,36097,36100,36102,36105,36108],{"class":117,"line":16549},[115,36093,36094],{"class":132},"            \"filename\"",[115,36096,2513],{"class":125},[115,36098,36099],{"class":121},"f",[115,36101,331],{"class":132},[115,36103,36104],{"class":202},"{LOG_DIR}",[115,36106,36107],{"class":132},"\u002Fdjango.log\"",[115,36109,3354],{"class":125},[115,36111,36112,36114,36116,36118],{"class":117,"line":16555},[115,36113,36060],{"class":132},[115,36115,2513],{"class":125},[115,36117,36065],{"class":132},[115,36119,3354],{"class":125},[115,36121,36122],{"class":117,"line":16564},[115,36123,3398],{"class":125},[115,36125,36126],{"class":117,"line":16573},[115,36127,3403],{"class":125},[115,36129,36130,36133],{"class":117,"line":16582},[115,36131,36132],{"class":132},"    \"root\"",[115,36134,3374],{"class":125},[115,36136,36137,36140,36142,36145,36147,36149,36151,36154,36156,36158],{"class":117,"line":16587},[115,36138,36139],{"class":132},"        \"handlers\"",[115,36141,2541],{"class":125},[115,36143,36144],{"class":132},"\"file\"",[115,36146,10861],{"class":125},[115,36148,10833],{"class":121},[115,36150,35930],{"class":202},[115,36152,36153],{"class":121}," else",[115,36155,7493],{"class":125},[115,36157,3427],{"class":132},[115,36159,3430],{"class":125},[115,36161,36162,36165,36167,36169],{"class":117,"line":16596},[115,36163,36164],{"class":132},"        \"level\"",[115,36166,2513],{"class":125},[115,36168,35886],{"class":202},[115,36170,3354],{"class":125},[115,36172,36173],{"class":117,"line":16609},[115,36174,3403],{"class":125},[115,36176,36177,36179],{"class":117,"line":16614},[115,36178,3408],{"class":132},[115,36180,3374],{"class":125},[115,36182,36183,36186],{"class":117,"line":16624},[115,36184,36185],{"class":132},"        \"django\"",[115,36187,3374],{"class":125},[115,36189,36190,36192,36194,36196,36198,36200,36202,36204,36206,36208],{"class":117,"line":16632},[115,36191,3422],{"class":132},[115,36193,2541],{"class":125},[115,36195,36144],{"class":132},[115,36197,10861],{"class":125},[115,36199,10833],{"class":121},[115,36201,35930],{"class":202},[115,36203,36153],{"class":121},[115,36205,7493],{"class":125},[115,36207,3427],{"class":132},[115,36209,3430],{"class":125},[115,36211,36212,36214,36216,36218],{"class":117,"line":16640},[115,36213,3435],{"class":132},[115,36215,2513],{"class":125},[115,36217,35898],{"class":132},[115,36219,3354],{"class":125},[115,36221,36222,36224,36226,36228],{"class":117,"line":16646},[115,36223,3447],{"class":132},[115,36225,2513],{"class":125},[115,36227,3364],{"class":202},[115,36229,3354],{"class":125},[115,36231,36232],{"class":117,"line":16651},[115,36233,3398],{"class":125},[115,36235,36236,36239],{"class":117,"line":16656},[115,36237,36238],{"class":132},"        \"django.request\"",[115,36240,3374],{"class":125},[115,36242,36243,36245,36247,36249,36251,36253,36255,36257,36259,36261],{"class":117,"line":16666},[115,36244,3422],{"class":132},[115,36246,2541],{"class":125},[115,36248,36144],{"class":132},[115,36250,10861],{"class":125},[115,36252,10833],{"class":121},[115,36254,35930],{"class":202},[115,36256,36153],{"class":121},[115,36258,7493],{"class":125},[115,36260,3427],{"class":132},[115,36262,3430],{"class":125},[115,36264,36265,36267,36269,36272],{"class":117,"line":16674},[115,36266,3435],{"class":132},[115,36268,2513],{"class":125},[115,36270,36271],{"class":132},"\"ERROR\"",[115,36273,3354],{"class":125},[115,36275,36276,36278,36280,36282],{"class":117,"line":16687},[115,36277,3447],{"class":132},[115,36279,2513],{"class":125},[115,36281,3364],{"class":202},[115,36283,3354],{"class":125},[115,36285,36286],{"class":117,"line":16692},[115,36287,3398],{"class":125},[115,36289,36290,36293],{"class":117,"line":16697},[115,36291,36292],{"class":132},"        \"django.server\"",[115,36294,3374],{"class":125},[115,36296,36297,36299,36301,36303,36305,36307,36309,36311,36313,36315],{"class":117,"line":16702},[115,36298,3422],{"class":132},[115,36300,2541],{"class":125},[115,36302,36144],{"class":132},[115,36304,10861],{"class":125},[115,36306,10833],{"class":121},[115,36308,35930],{"class":202},[115,36310,36153],{"class":121},[115,36312,7493],{"class":125},[115,36314,3427],{"class":132},[115,36316,3430],{"class":125},[115,36318,36319,36321,36323,36325],{"class":117,"line":16711},[115,36320,3435],{"class":132},[115,36322,2513],{"class":125},[115,36324,3440],{"class":132},[115,36326,3354],{"class":125},[115,36328,36329,36331,36333,36335],{"class":117,"line":16719},[115,36330,3447],{"class":132},[115,36332,2513],{"class":125},[115,36334,3364],{"class":202},[115,36336,3354],{"class":125},[115,36338,36339],{"class":117,"line":16732},[115,36340,3398],{"class":125},[115,36342,36343,36346],{"class":117,"line":16745},[115,36344,36345],{"class":132},"        \"myapp\"",[115,36347,3374],{"class":125},[115,36349,36350,36352,36354,36356,36358,36360,36362,36364,36366,36368],{"class":117,"line":16751},[115,36351,3422],{"class":132},[115,36353,2541],{"class":125},[115,36355,36144],{"class":132},[115,36357,10861],{"class":125},[115,36359,10833],{"class":121},[115,36361,35930],{"class":202},[115,36363,36153],{"class":121},[115,36365,7493],{"class":125},[115,36367,3427],{"class":132},[115,36369,3430],{"class":125},[115,36371,36372,36374,36376,36378],{"class":117,"line":28158},[115,36373,3435],{"class":132},[115,36375,2513],{"class":125},[115,36377,35886],{"class":202},[115,36379,3354],{"class":125},[115,36381,36382,36384,36386,36388],{"class":117,"line":28164},[115,36383,3447],{"class":132},[115,36385,2513],{"class":125},[115,36387,3364],{"class":202},[115,36389,3354],{"class":125},[115,36391,36392],{"class":117,"line":28170},[115,36393,3398],{"class":125},[115,36395,36396],{"class":117,"line":28175},[115,36397,3403],{"class":125},[115,36399,36400],{"class":117,"line":28187},[115,36401,2323],{"class":125},[16,36403,36404],{},"This gives you:",[63,36406,36407,36410,36413,36419],{},[66,36408,36409],{},"consistent formatting",[66,36411,36412],{},"one destination selected by environment",[66,36414,36415,36416],{},"visible request errors through ",[20,36417,36418],{},"django.request",[66,36420,36421,36422,36425],{},"a dedicated app logger (",[20,36423,36424],{},"myapp",") for your own code",[16,36427,36428,36429,211],{},"Use your real project or app name instead of ",[20,36430,36424],{},[16,36432,36433],{},[1226,36434,36435],{},"Verification check",[16,36437,36438],{},"Run Django shell in production and emit a test line:",[106,36440,36442],{"className":108,"code":36441,"language":110,"meta":111,"style":111},"python manage.py shell -c \"import logging; logging.getLogger('myapp').warning('production log test')\"\n",[20,36443,36444],{"__ignoreMap":111},[115,36445,36446,36448,36450,36452,36454],{"class":117,"line":118},[115,36447,1114],{"class":262},[115,36449,1117],{"class":132},[115,36451,9071],{"class":132},[115,36453,1024],{"class":202},[115,36455,36456],{"class":132}," \"import logging; logging.getLogger('myapp').warning('production log test')\"\n",[16,36458,36459],{},"Then inspect the destination:",[106,36461,36463],{"className":108,"code":36462,"language":110,"meta":111,"style":111},"journalctl -u \u003Cyour-gunicorn-service-name> -f\n",[20,36464,36465],{"__ignoreMap":111},[115,36466,36467,36469,36471,36473,36476,36478,36480],{"class":117,"line":118},[115,36468,2785],{"class":262},[115,36470,2788],{"class":202},[115,36472,7691],{"class":121},[115,36474,36475],{"class":132},"your-gunicorn-service-nam",[115,36477,17377],{"class":125},[115,36479,22818],{"class":121},[115,36481,36482],{"class":202}," -f\n",[16,36484,36485],{},"or",[106,36487,36489],{"className":108,"code":36488,"language":110,"meta":111,"style":111},"tail -f \u002Fvar\u002Flog\u002Fmyapp\u002Fdjango.log\n",[20,36490,36491],{"__ignoreMap":111},[115,36492,36493,36496,36498],{"class":117,"line":118},[115,36494,36495],{"class":262},"tail",[115,36497,2777],{"class":202},[115,36499,36500],{"class":132}," \u002Fvar\u002Flog\u002Fmyapp\u002Fdjango.log\n",[16,36502,36503],{},"If nothing appears, stop and fix the destination before deploying further changes.",[23099,36505],{},[52,36507,36509],{"id":36508},"_3-keep-gunicorn-logs-separate-from-django-logs","3. Keep Gunicorn logs separate from Django logs",[16,36511,36512],{},"Gunicorn should manage its own access and error logging. Do not rely on Django to replace that.",[16,36514,36515],{},"Example Gunicorn command:",[106,36517,36519],{"className":108,"code":36518,"language":110,"meta":111,"style":111},"gunicorn config.wsgi:application \\\n  --bind 127.0.0.1:8000 \\\n  --workers 3 \\\n  --access-logfile - \\\n  --error-logfile - \\\n  --log-level info\n",[20,36520,36521,36529,36538,36548,36557,36566],{"__ignoreMap":111},[115,36522,36523,36525,36527],{"class":117,"line":118},[115,36524,14954],{"class":262},[115,36526,32822],{"class":132},[115,36528,317],{"class":202},[115,36530,36531,36534,36536],{"class":117,"line":136},[115,36532,36533],{"class":202},"  --bind",[115,36535,23608],{"class":132},[115,36537,317],{"class":202},[115,36539,36540,36543,36546],{"class":117,"line":149},[115,36541,36542],{"class":202},"  --workers",[115,36544,36545],{"class":202}," 3",[115,36547,317],{"class":202},[115,36549,36550,36553,36555],{"class":117,"line":162},[115,36551,36552],{"class":202},"  --access-logfile",[115,36554,4009],{"class":132},[115,36556,317],{"class":202},[115,36558,36559,36562,36564],{"class":117,"line":175},[115,36560,36561],{"class":202},"  --error-logfile",[115,36563,4009],{"class":132},[115,36565,317],{"class":202},[115,36567,36568,36571],{"class":117,"line":350},[115,36569,36570],{"class":202},"  --log-level",[115,36572,36573],{"class":132}," info\n",[16,36575,36576,36577,36580],{},"With ",[20,36578,36579],{},"-",", Gunicorn writes to stdout\u002Fstderr, which systemd or Docker can capture.",[16,36582,36583],{},"If you use systemd:",[106,36585,36587],{"className":2026,"code":36586,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=gunicorn for myapp\nAfter=network.target\n\n[Service]\nUser=myapp\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironment=\"DJANGO_SETTINGS_MODULE=config.settings.production\"\nEnvironment=\"DJANGO_LOG_TO_FILE=0\"\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3 --access-logfile - --error-logfile - --log-level info\nRestart=always\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\n",[20,36588,36589,36593,36600,36606,36610,36614,36621,36627,36633,36643,36652,36659,36665,36673,36680,36684,36688],{"__ignoreMap":111},[115,36590,36591],{"class":117,"line":118},[115,36592,2035],{"class":262},[115,36594,36595,36597],{"class":117,"line":136},[115,36596,2040],{"class":121},[115,36598,36599],{"class":125},"=gunicorn for myapp\n",[115,36601,36602,36604],{"class":117,"line":149},[115,36603,2048],{"class":121},[115,36605,2051],{"class":125},[115,36607,36608],{"class":117,"line":162},[115,36609,310],{"emptyLinePlaceholder":309},[115,36611,36612],{"class":117,"line":175},[115,36613,2060],{"class":262},[115,36615,36616,36618],{"class":117,"line":350},[115,36617,2065],{"class":121},[115,36619,36620],{"class":125},"=myapp\n",[115,36622,36623,36625],{"class":117,"line":365},[115,36624,2073],{"class":121},[115,36626,2076],{"class":125},[115,36628,36629,36631],{"class":117,"line":380},[115,36630,2081],{"class":121},[115,36632,4905],{"class":125},[115,36634,36635,36638,36640],{"class":117,"line":487},[115,36636,36637],{"class":121},"Environment",[115,36639,129],{"class":125},[115,36641,36642],{"class":132},"\"DJANGO_SETTINGS_MODULE=config.settings.production\"\n",[115,36644,36645,36647,36649],{"class":117,"line":2095},[115,36646,36637],{"class":121},[115,36648,129],{"class":125},[115,36650,36651],{"class":132},"\"DJANGO_LOG_TO_FILE=0\"\n",[115,36653,36654,36656],{"class":117,"line":2104},[115,36655,2107],{"class":121},[115,36657,36658],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3 --access-logfile - --error-logfile - --log-level info\n",[115,36660,36661,36663],{"class":117,"line":2113},[115,36662,2116],{"class":121},[115,36664,4932],{"class":125},[115,36666,36667,36670],{"class":117,"line":2122},[115,36668,36669],{"class":121},"StandardOutput",[115,36671,36672],{"class":125},"=journal\n",[115,36674,36675,36678],{"class":117,"line":2131},[115,36676,36677],{"class":121},"StandardError",[115,36679,36672],{"class":125},[115,36681,36682],{"class":117,"line":2136},[115,36683,310],{"emptyLinePlaceholder":309},[115,36685,36686],{"class":117,"line":2142},[115,36687,2139],{"class":262},[115,36689,36690,36692],{"class":117,"line":2273},[115,36691,2145],{"class":121},[115,36693,2148],{"class":125},[16,36695,36696],{},"Reload and restart safely:",[106,36698,36700],{"className":108,"code":36699,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl restart \u003Cyour-gunicorn-service-name>\nsudo systemctl status \u003Cyour-gunicorn-service-name>\n",[20,36701,36702,36710,36726],{"__ignoreMap":111},[115,36703,36704,36706,36708],{"class":117,"line":118},[115,36705,2001],{"class":262},[115,36707,3480],{"class":132},[115,36709,4984],{"class":132},[115,36711,36712,36714,36716,36718,36720,36722,36724],{"class":117,"line":136},[115,36713,2001],{"class":262},[115,36715,3480],{"class":132},[115,36717,3483],{"class":132},[115,36719,7691],{"class":121},[115,36721,36475],{"class":132},[115,36723,17377],{"class":125},[115,36725,17380],{"class":121},[115,36727,36728,36730,36732,36734,36736,36738,36740],{"class":117,"line":149},[115,36729,2001],{"class":262},[115,36731,3480],{"class":132},[115,36733,1984],{"class":132},[115,36735,7691],{"class":121},[115,36737,36475],{"class":132},[115,36739,17377],{"class":125},[115,36741,17380],{"class":121},[16,36743,36744],{},[1226,36745,36435],{},[16,36747,36748],{},"Follow service logs:",[106,36750,36751],{"className":108,"code":36462,"language":110,"meta":111,"style":111},[20,36752,36753],{"__ignoreMap":111},[115,36754,36755,36757,36759,36761,36763,36765,36767],{"class":117,"line":118},[115,36756,2785],{"class":262},[115,36758,2788],{"class":202},[115,36760,7691],{"class":121},[115,36762,36475],{"class":132},[115,36764,17377],{"class":125},[115,36766,22818],{"class":121},[115,36768,36482],{"class":202},[16,36770,36771],{},"You should see both Gunicorn process logs and Django console logs if Django is writing to stdout.",[16,36773,36774],{},[1226,36775,4956],{},[16,36777,36778,36779,36781],{},"If the service fails after changing logging or ",[20,36780,2107],{},", restore the previous unit file and restart:",[106,36783,36785],{"className":108,"code":36784,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl restart \u003Cyour-gunicorn-service-name>\n",[20,36786,36787,36795],{"__ignoreMap":111},[115,36788,36789,36791,36793],{"class":117,"line":118},[115,36790,2001],{"class":262},[115,36792,3480],{"class":132},[115,36794,4984],{"class":132},[115,36796,36797,36799,36801,36803,36805,36807,36809],{"class":117,"line":136},[115,36798,2001],{"class":262},[115,36800,3480],{"class":132},[115,36802,3483],{"class":132},[115,36804,7691],{"class":121},[115,36806,36475],{"class":132},[115,36808,17377],{"class":125},[115,36810,17380],{"class":121},[16,36812,36813],{},"Then confirm the service is healthy with:",[106,36815,36817],{"className":108,"code":36816,"language":110,"meta":111,"style":111},"sudo systemctl status \u003Cyour-gunicorn-service-name>\njournalctl -u \u003Cyour-gunicorn-service-name> -b\n",[20,36818,36819,36835],{"__ignoreMap":111},[115,36820,36821,36823,36825,36827,36829,36831,36833],{"class":117,"line":118},[115,36822,2001],{"class":262},[115,36824,3480],{"class":132},[115,36826,1984],{"class":132},[115,36828,7691],{"class":121},[115,36830,36475],{"class":132},[115,36832,17377],{"class":125},[115,36834,17380],{"class":121},[115,36836,36837,36839,36841,36843,36845,36847,36849],{"class":117,"line":136},[115,36838,2785],{"class":262},[115,36840,2788],{"class":202},[115,36842,7691],{"class":121},[115,36844,36475],{"class":132},[115,36846,17377],{"class":125},[115,36848,22818],{"class":121},[115,36850,36851],{"class":202}," -b\n",[23099,36853],{},[52,36855,36857],{"id":36856},"_4-if-you-use-file-logging-add-rotation-and-permissions","4. If you use file logging, add rotation and permissions",[16,36859,36860],{},"If you write to files, do not leave them unmanaged.",[16,36862,36863],{},"Create the directory with restricted access:",[106,36865,36867],{"className":108,"code":36866,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fvar\u002Flog\u002Fmyapp\nsudo chown myapp:www-data \u002Fvar\u002Flog\u002Fmyapp\nsudo chmod 750 \u002Fvar\u002Flog\u002Fmyapp\n",[20,36868,36869,36880,36891],{"__ignoreMap":111},[115,36870,36871,36873,36875,36877],{"class":117,"line":118},[115,36872,2001],{"class":262},[115,36874,6721],{"class":132},[115,36876,1001],{"class":202},[115,36878,36879],{"class":132}," \u002Fvar\u002Flog\u002Fmyapp\n",[115,36881,36882,36884,36886,36889],{"class":117,"line":136},[115,36883,2001],{"class":262},[115,36885,6733],{"class":132},[115,36887,36888],{"class":132}," myapp:www-data",[115,36890,36879],{"class":132},[115,36892,36893,36895,36897,36900],{"class":117,"line":149},[115,36894,2001],{"class":262},[115,36896,12480],{"class":132},[115,36898,36899],{"class":202}," 750",[115,36901,36879],{"class":132},[16,36903,29658,36904,36907],{},[20,36905,36906],{},"logrotate"," rule:",[106,36909,36911],{"className":8444,"code":36910,"language":8446,"meta":111,"style":111},"\u002Fvar\u002Flog\u002Fmyapp\u002F*.log {\n    daily\n    rotate 14\n    compress\n    delaycompress\n    missingok\n    notifempty\n    create 0640 myapp www-data\n}\n",[20,36912,36913,36918,36923,36928,36933,36938,36943,36948,36953],{"__ignoreMap":111},[115,36914,36915],{"class":117,"line":118},[115,36916,36917],{},"\u002Fvar\u002Flog\u002Fmyapp\u002F*.log {\n",[115,36919,36920],{"class":117,"line":136},[115,36921,36922],{},"    daily\n",[115,36924,36925],{"class":117,"line":149},[115,36926,36927],{},"    rotate 14\n",[115,36929,36930],{"class":117,"line":162},[115,36931,36932],{},"    compress\n",[115,36934,36935],{"class":117,"line":175},[115,36936,36937],{},"    delaycompress\n",[115,36939,36940],{"class":117,"line":350},[115,36941,36942],{},"    missingok\n",[115,36944,36945],{"class":117,"line":365},[115,36946,36947],{},"    notifempty\n",[115,36949,36950],{"class":117,"line":380},[115,36951,36952],{},"    create 0640 myapp www-data\n",[115,36954,36955],{"class":117,"line":487},[115,36956,2323],{},[16,36958,36959],{},"Save that as:",[106,36961,36963],{"className":108,"code":36962,"language":110,"meta":111,"style":111},"\u002Fetc\u002Flogrotate.d\u002Fmyapp\n",[20,36964,36965],{"__ignoreMap":111},[115,36966,36967],{"class":117,"line":118},[115,36968,36962],{"class":262},[16,36970,36971],{},"Test the syntax:",[106,36973,36975],{"className":108,"code":36974,"language":110,"meta":111,"style":111},"sudo logrotate -d \u002Fetc\u002Flogrotate.d\u002Fmyapp\n",[20,36976,36977],{"__ignoreMap":111},[115,36978,36979,36981,36984,36986],{"class":117,"line":118},[115,36980,2001],{"class":262},[115,36982,36983],{"class":132}," logrotate",[115,36985,1019],{"class":202},[115,36987,36988],{"class":132}," \u002Fetc\u002Flogrotate.d\u002Fmyapp\n",[16,36990,36991,36992,36994,36995,36998,36999,37002],{},"If you rotate logs externally with ",[20,36993,36906],{},", prefer ",[20,36996,36997],{},"logging.handlers.WatchedFileHandler"," on Linux. A plain ",[20,37000,37001],{},"FileHandler"," may keep writing to the old rotated file until the process restarts or reopens the file.",[16,37004,37005],{},"Watch the app log:",[106,37007,37008],{"className":108,"code":36488,"language":110,"meta":111,"style":111},[20,37009,37010],{"__ignoreMap":111},[115,37011,37012,37014,37016],{"class":117,"line":118},[115,37013,36495],{"class":262},[115,37015,2777],{"class":202},[115,37017,36500],{"class":132},[16,37019,37020],{},[1226,37021,36435],{},[16,37023,37024],{},"Confirm the Django process can write to the file after deploy. Permission errors here are common.",[16,37026,37027],{},"If your first log file is created by the app process, check its mode as well:",[106,37029,37031],{"className":108,"code":37030,"language":110,"meta":111,"style":111},"ls -l \u002Fvar\u002Flog\u002Fmyapp\u002Fdjango.log\n",[20,37032,37033],{"__ignoreMap":111},[115,37034,37035,37037,37039],{"class":117,"line":118},[115,37036,532],{"class":262},[115,37038,14881],{"class":202},[115,37040,36500],{"class":132},[16,37042,37043],{},[1226,37044,4956],{},[16,37046,37047],{},"If file writes fail in production, switch back to console or journald by setting:",[106,37049,37051],{"className":108,"code":37050,"language":110,"meta":111,"style":111},"DJANGO_LOG_TO_FILE=0\n",[20,37052,37053],{"__ignoreMap":111},[115,37054,37055,37058,37060],{"class":117,"line":118},[115,37056,37057],{"class":125},"DJANGO_LOG_TO_FILE",[115,37059,129],{"class":121},[115,37061,37062],{"class":132},"0\n",[16,37064,37065],{},"Then restart your Gunicorn service.",[23099,37067],{},[52,37069,37071],{"id":37070},"_5-add-security-focused-logging-without-leaking-data","5. Add security-focused logging without leaking data",[16,37073,37074],{},"You should log security-relevant events, but not sensitive content.",[16,37076,37077],{},"Good things to notice in logs:",[63,37079,37080,37083,37086,37089,37092],{},[66,37081,37082],{},"repeated 403 or permission failures",[66,37084,37085],{},"suspicious host header errors",[66,37087,37088],{},"admin login failures",[66,37090,37091],{},"5xx spikes",[66,37093,37094],{},"unexpected exception traces",[16,37096,35389],{},[63,37098,37099,37105,37108,37111,37114],{},[66,37100,37101,37104],{},[20,37102,37103],{},"Authorization"," headers",[66,37106,37107],{},"session cookies",[66,37109,37110],{},"passwords",[66,37112,37113],{},"reset tokens",[66,37115,37116],{},"full request bodies containing user data",[16,37118,37119],{},"A minimal redaction filter for custom logs looks like this:",[106,37121,37123],{"className":2369,"code":37122,"language":1114,"meta":111,"style":111},"import logging\n\nclass RedactSensitiveDataFilter(logging.Filter):\n    SENSITIVE_KEYS = {\"password\", \"token\", \"access_token\", \"refresh_token\", \"authorization\", \"cookie\"}\n\n    def filter(self, record):\n        if isinstance(record.args, dict):\n            record.args = {\n                k: (\"***\" if str(k).lower() in self.SENSITIVE_KEYS else v)\n                for k, v in record.args.items()\n            }\n        return True\n",[20,37124,37125,37131,37135,37157,37197,37201,37212,37228,37237,37269,37282,37287],{"__ignoreMap":111},[115,37126,37127,37129],{"class":117,"line":118},[115,37128,5613],{"class":121},[115,37130,8775],{"class":125},[115,37132,37133],{"class":117,"line":136},[115,37134,310],{"emptyLinePlaceholder":309},[115,37136,37137,37140,37143,37146,37149,37151,37154],{"class":117,"line":149},[115,37138,37139],{"class":121},"class",[115,37141,37142],{"class":262}," RedactSensitiveDataFilter",[115,37144,37145],{"class":125},"(",[115,37147,37148],{"class":262},"logging",[115,37150,211],{"class":125},[115,37152,37153],{"class":262},"Filter",[115,37155,37156],{"class":125},"):\n",[115,37158,37159,37162,37164,37167,37170,37172,37175,37177,37180,37182,37185,37187,37190,37192,37195],{"class":117,"line":162},[115,37160,37161],{"class":202},"    SENSITIVE_KEYS",[115,37163,2380],{"class":121},[115,37165,37166],{"class":125}," {",[115,37168,37169],{"class":132},"\"password\"",[115,37171,1153],{"class":125},[115,37173,37174],{"class":132},"\"token\"",[115,37176,1153],{"class":125},[115,37178,37179],{"class":132},"\"access_token\"",[115,37181,1153],{"class":125},[115,37183,37184],{"class":132},"\"refresh_token\"",[115,37186,1153],{"class":125},[115,37188,37189],{"class":132},"\"authorization\"",[115,37191,1153],{"class":125},[115,37193,37194],{"class":132},"\"cookie\"",[115,37196,2323],{"class":125},[115,37198,37199],{"class":117,"line":175},[115,37200,310],{"emptyLinePlaceholder":309},[115,37202,37203,37206,37209],{"class":117,"line":350},[115,37204,37205],{"class":121},"    def",[115,37207,37208],{"class":202}," filter",[115,37210,37211],{"class":125},"(self, record):\n",[115,37213,37214,37217,37220,37223,37226],{"class":117,"line":365},[115,37215,37216],{"class":121},"        if",[115,37218,37219],{"class":202}," isinstance",[115,37221,37222],{"class":125},"(record.args, ",[115,37224,37225],{"class":202},"dict",[115,37227,37156],{"class":125},[115,37229,37230,37233,37235],{"class":117,"line":380},[115,37231,37232],{"class":125},"            record.args ",[115,37234,129],{"class":121},[115,37236,2166],{"class":125},[115,37238,37239,37242,37245,37248,37251,37254,37256,37259,37261,37264,37266],{"class":117,"line":487},[115,37240,37241],{"class":125},"                k: (",[115,37243,37244],{"class":132},"\"***\"",[115,37246,37247],{"class":121}," if",[115,37249,37250],{"class":202}," str",[115,37252,37253],{"class":125},"(k).lower() ",[115,37255,18262],{"class":121},[115,37257,37258],{"class":202}," self",[115,37260,211],{"class":125},[115,37262,37263],{"class":202},"SENSITIVE_KEYS",[115,37265,36153],{"class":121},[115,37267,37268],{"class":125}," v)\n",[115,37270,37271,37274,37277,37279],{"class":117,"line":2095},[115,37272,37273],{"class":121},"                for",[115,37275,37276],{"class":125}," k, v ",[115,37278,18262],{"class":121},[115,37280,37281],{"class":125}," record.args.items()\n",[115,37283,37284],{"class":117,"line":2104},[115,37285,37286],{"class":125},"            }\n",[115,37288,37289,37291],{"class":117,"line":2113},[115,37290,6812],{"class":121},[115,37292,2412],{"class":202},[16,37294,37295],{},"Wire it into Django logging explicitly:",[106,37297,37299],{"className":2369,"code":37298,"language":1114,"meta":111,"style":111},"LOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"filters\": {\n        \"redact_sensitive\": {\n            \"()\": \"myapp.logging_filters.RedactSensitiveDataFilter\",\n        },\n    },\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"filters\": [\"redact_sensitive\"],\n        },\n    },\n}\n",[20,37300,37301,37309,37319,37329,37336,37343,37355,37359,37363,37369,37375,37385,37397,37401,37405],{"__ignoreMap":111},[115,37302,37303,37305,37307],{"class":117,"line":118},[115,37304,3337],{"class":202},[115,37306,2380],{"class":121},[115,37308,2166],{"class":125},[115,37310,37311,37313,37315,37317],{"class":117,"line":136},[115,37312,3346],{"class":132},[115,37314,2513],{"class":125},[115,37316,3351],{"class":202},[115,37318,3354],{"class":125},[115,37320,37321,37323,37325,37327],{"class":117,"line":149},[115,37322,3359],{"class":132},[115,37324,2513],{"class":125},[115,37326,3364],{"class":202},[115,37328,3354],{"class":125},[115,37330,37331,37334],{"class":117,"line":162},[115,37332,37333],{"class":132},"    \"filters\"",[115,37335,3374],{"class":125},[115,37337,37338,37341],{"class":117,"line":175},[115,37339,37340],{"class":132},"        \"redact_sensitive\"",[115,37342,3374],{"class":125},[115,37344,37345,37348,37350,37353],{"class":117,"line":350},[115,37346,37347],{"class":132},"            \"()\"",[115,37349,2513],{"class":125},[115,37351,37352],{"class":132},"\"myapp.logging_filters.RedactSensitiveDataFilter\"",[115,37354,3354],{"class":125},[115,37356,37357],{"class":117,"line":365},[115,37358,3398],{"class":125},[115,37360,37361],{"class":117,"line":380},[115,37362,3403],{"class":125},[115,37364,37365,37367],{"class":117,"line":487},[115,37366,3371],{"class":132},[115,37368,3374],{"class":125},[115,37370,37371,37373],{"class":117,"line":2095},[115,37372,3379],{"class":132},[115,37374,3374],{"class":125},[115,37376,37377,37379,37381,37383],{"class":117,"line":2104},[115,37378,3386],{"class":132},[115,37380,2513],{"class":125},[115,37382,3391],{"class":132},[115,37384,3354],{"class":125},[115,37386,37387,37390,37392,37395],{"class":117,"line":2113},[115,37388,37389],{"class":132},"            \"filters\"",[115,37391,2541],{"class":125},[115,37393,37394],{"class":132},"\"redact_sensitive\"",[115,37396,3430],{"class":125},[115,37398,37399],{"class":117,"line":2122},[115,37400,3398],{"class":125},[115,37402,37403],{"class":117,"line":2131},[115,37404,3403],{"class":125},[115,37406,37407],{"class":117,"line":2136},[115,37408,2323],{"class":125},[16,37410,37411,37412,37415],{},"This example is intentionally limited. It only helps when your log call passes a dictionary in ",[20,37413,37414],{},"record.args",". It does not automatically sanitize exception messages, arbitrary strings, headers, or request bodies. The main control is still to avoid logging sensitive fields in application code in the first place.",[23099,37417],{},[52,37419,37421],{"id":37420},"_6-correlate-django-gunicorn-and-nginx-logs","6. Correlate Django, Gunicorn, and Nginx logs",[16,37423,37424],{},"Each layer should log different things:",[1850,37426,37428],{"id":37427},"django-should-log","Django should log",[63,37430,37431,37434,37437,37440],{},[66,37432,37433],{},"application warnings and errors",[66,37435,37436],{},"exception traces",[66,37438,37439],{},"important business-process failures",[66,37441,37442],{},"selected security-relevant events",[1850,37444,37446],{"id":37445},"gunicorn-or-uvicorn-should-log","Gunicorn or Uvicorn should log",[63,37448,37449,37452,37455,37458],{},[66,37450,37451],{},"worker starts and exits",[66,37453,37454],{},"process crashes",[66,37456,37457],{},"timeouts",[66,37459,37460],{},"access logs if enabled at the app server layer",[1850,37462,37464],{"id":37463},"nginx-should-log","Nginx should log",[63,37466,37467,37470,37473,37476],{},[66,37468,37469],{},"client access",[66,37471,37472],{},"upstream failures",[66,37474,37475],{},"502\u002F504 proxy issues",[66,37477,37478],{},"TLS and connection-level problems",[16,37480,37481],{},"Useful inspection commands:",[106,37483,37485],{"className":108,"code":37484,"language":110,"meta":111,"style":111},"journalctl -u \u003Cyour-gunicorn-service-name> -f\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\ngrep -R \"Traceback\" \u002Fvar\u002Flog\u002Fmyapp \u002Fvar\u002Flog\u002Fnginx 2>\u002Fdev\u002Fnull\n",[20,37486,37487,37503,37511,37519],{"__ignoreMap":111},[115,37488,37489,37491,37493,37495,37497,37499,37501],{"class":117,"line":118},[115,37490,2785],{"class":262},[115,37492,2788],{"class":202},[115,37494,7691],{"class":121},[115,37496,36475],{"class":132},[115,37498,17377],{"class":125},[115,37500,22818],{"class":121},[115,37502,36482],{"class":202},[115,37504,37505,37507,37509],{"class":117,"line":136},[115,37506,36495],{"class":262},[115,37508,2777],{"class":202},[115,37510,13195],{"class":132},[115,37512,37513,37515,37517],{"class":117,"line":149},[115,37514,36495],{"class":262},[115,37516,2777],{"class":202},[115,37518,30354],{"class":132},[115,37520,37521,37524,37526,37529,37532,37535,37538],{"class":117,"line":162},[115,37522,37523],{"class":262},"grep",[115,37525,6736],{"class":202},[115,37527,37528],{"class":132}," \"Traceback\"",[115,37530,37531],{"class":132}," \u002Fvar\u002Flog\u002Fmyapp",[115,37533,37534],{"class":132}," \u002Fvar\u002Flog\u002Fnginx",[115,37536,37537],{"class":121}," 2>",[115,37539,7694],{"class":132},[16,37541,37542],{},"During an incident, correlate by timestamp, request path, client IP, and upstream status. If you later add request IDs, this becomes easier, but a basic timestamp-based setup is already useful.",[23099,37544],{},[52,37546,37548],{"id":37547},"_7-verify-logging-in-production","7. Verify logging in production",[16,37550,37551],{},"Do not assume logging works because the app starts.",[1850,37553,37555],{"id":37554},"trigger-a-test-app-log","Trigger a test app log",[106,37557,37559],{"className":108,"code":37558,"language":110,"meta":111,"style":111},"python manage.py shell -c \"import logging; logging.getLogger('myapp').error('error-path verification')\"\n",[20,37560,37561],{"__ignoreMap":111},[115,37562,37563,37565,37567,37569,37571],{"class":117,"line":118},[115,37564,1114],{"class":262},[115,37566,1117],{"class":132},[115,37568,9071],{"class":132},[115,37570,1024],{"class":202},[115,37572,37573],{"class":132}," \"import logging; logging.getLogger('myapp').error('error-path verification')\"\n",[1850,37575,37577],{"id":37576},"trigger-a-controlled-exception","Trigger a controlled exception",[16,37579,37580],{},"Use a temporary internal-only test view or a dedicated management command in a maintenance window, then confirm the exception appears in the Django log destination.",[16,37582,37583],{},"Example temporary view:",[106,37585,37587],{"className":2369,"code":37586,"language":1114,"meta":111,"style":111},"from django.http import HttpResponse\n\ndef logging_test_error(request):\n    raise RuntimeError(\"controlled production logging test\")\n",[20,37588,37589,37599,37603,37612],{"__ignoreMap":111},[115,37590,37591,37593,37595,37597],{"class":117,"line":118},[115,37592,5621],{"class":121},[115,37594,17240],{"class":125},[115,37596,5613],{"class":121},[115,37598,17245],{"class":125},[115,37600,37601],{"class":117,"line":136},[115,37602,310],{"emptyLinePlaceholder":309},[115,37604,37605,37607,37610],{"class":117,"line":149},[115,37606,8808],{"class":121},[115,37608,37609],{"class":262}," logging_test_error",[115,37611,17271],{"class":125},[115,37613,37614,37617,37620,37622,37625],{"class":117,"line":162},[115,37615,37616],{"class":121},"    raise",[115,37618,37619],{"class":202}," RuntimeError",[115,37621,37145],{"class":125},[115,37623,37624],{"class":132},"\"controlled production logging test\"",[115,37626,2394],{"class":125},[16,37628,37629],{},"Map it to an internal-only URL, request it once, confirm the exception is logged, then remove it.",[16,37631,37632,37633,37636],{},"If you do not want a temporary URL, use a management command that logs with ",[20,37634,37635],{},"logger.exception(...)"," inside a handled test block.",[1850,37638,37640],{"id":37639},"confirm-the-expected-destination","Confirm the expected destination",[63,37642,37643,37649,37655],{},[66,37644,37645,37646],{},"journald: ",[20,37647,37648],{},"journalctl -u \u003Cyour-gunicorn-service-name> -f",[66,37650,37651,37652],{},"file: ",[20,37653,37654],{},"tail -f \u002Fvar\u002Flog\u002Fmyapp\u002Fdjango.log",[66,37656,37657,37658],{},"Docker: ",[20,37659,37660],{},"docker logs \u003Ccontainer> --follow",[1850,37662,37664],{"id":37663},"confirm-rotation-and-permissions","Confirm rotation and permissions",[16,37666,37667],{},"For file logging:",[63,37669,37670,37673,37676,37681],{},[66,37671,37672],{},"log files belong to the app user",[66,37674,37675],{},"mode prevents world-read access",[66,37677,37678,37680],{},[20,37679,36906],{}," config is valid",[66,37682,37683],{},"disk usage is monitored",[11,37685,1321],{"id":1320},[16,37687,37688,37689,37692],{},"A good ",[1226,37690,37691],{},"Django production logging"," setup is mostly about operational clarity.",[16,37694,37695],{},"Sending logs to stdout\u002Fstderr works well in containers and supervised environments because the runtime already knows how to capture and forward streams. On Linux VMs with systemd, journald gives you centralized service logs without extra file-handling logic. File logging is still valid, but only if you also manage ownership, rotation, retention, and disk growth.",[16,37697,37698],{},"Keeping Django logs separate from Gunicorn and Nginx logs matters because each layer answers different questions. Django helps with application exceptions, Gunicorn with worker behavior and process failures, and Nginx with edge and proxy failures. Mixing them into one destination without structure makes incident response slower.",[16,37700,37701,37704,37705,37708],{},[20,37702,37703],{},"disable_existing_loggers=False"," is usually the safer default in Django production logging because it avoids unexpectedly silencing framework loggers. Setting ",[20,37706,37707],{},"propagate=False"," on specific loggers helps prevent duplicate lines when both a named logger and the root logger handle the same event.",[11,37710,30532],{"id":30531},[63,37712,37713,37716,37719,37722,37725,37728,37731],{},[66,37714,37715],{},"If you deploy with Docker, prefer stdout\u002Fstderr only. Let the container runtime or platform handle rotation.",[66,37717,37718],{},"If logs suddenly spike, check for repeated 4xx\u002F5xx loops, health-check noise, or an exception inside middleware.",[66,37720,37721],{},"Logging full request bodies is especially risky for forms, authentication flows, and API endpoints.",[66,37723,37724],{},"Static and media requests usually belong in reverse-proxy access logs, not Django application logs.",[66,37726,37727],{},"If Gunicorn workers are timing out, look at both Gunicorn error logs and Nginx upstream errors; Django alone may not show the full failure path.",[66,37729,37730],{},"Forwarded headers and proxy configuration can affect what request context appears in logs. If request scheme, host, or client IP looks wrong, verify your reverse-proxy and Django proxy header settings.",[66,37732,37733],{},"Email-based admin alerts can exist as a secondary path, but they are not a replacement for searchable persistent logs.",[52,37735,37737],{"id":37736},"when-manual-logging-setup-becomes-repetitive","When manual logging setup becomes repetitive",[16,37739,37740,37741,37743,37744,37746],{},"Once you manage multiple Django services, this setup becomes repetitive fast. The parts worth standardizing first are the ",[20,37742,3337],{}," dict defaults, systemd logging behavior, log directory creation, and ",[20,37745,36906],{}," rules. That is a good point to move the manual baseline into reusable templates or deployment scripts so each project starts from the same hardened defaults.",[11,37748,1386],{"id":1385},[16,37750,37751,37752,211],{},"For the broader production baseline, see ",[1395,37753,3007],{"href":4418},[16,37755,37756,37757,3146,37759,211],{},"If you are building the common Linux stack around this, read ",[1395,37758,4425],{"href":2985},[1395,37760,37761],{"href":32218},"How to configure systemd for Django and Gunicorn",[16,37763,37764,37765,211],{},"If you are deciding between deployment interfaces first, see ",[1395,37766,3014],{"href":3013},[16,37768,37769,37770,211],{},"If you are already debugging upstream failures, continue with ",[1395,37771,37772],{"href":4455},"How to troubleshoot 502 Bad Gateway in Django with Nginx and Gunicorn",[11,37774,1420],{"id":1419},[52,37776,37778],{"id":37777},"should-django-production-logs-go-to-files-or-stdout","Should Django production logs go to files or stdout?",[16,37780,37781],{},"Use stdout\u002Fstderr for Docker, Kubernetes, and most supervised environments. Use journald on systemd-based Linux servers if you want service-level log collection without managing files directly. Use files only when you also configure permissions, rotation, and retention.",[52,37783,37785],{"id":37784},"what-log-level-should-i-use-for-django-in-production","What log level should I use for Django in production?",[16,37787,37788,37789,37791,37792,37794,37795,37797,37798,37800],{},"Start with ",[20,37790,5173],{}," for the main Django or app logger and ensure ",[20,37793,36418],{}," captures ",[20,37796,35685],{},". Avoid ",[20,37799,7350],{}," in production unless you are diagnosing a short-lived issue and can control volume and data exposure.",[52,37802,37804],{"id":37803},"how-do-i-stop-django-from-logging-sensitive-data","How do I stop Django from logging sensitive data?",[16,37806,37807],{},"Do not log secrets in application code. Avoid logging headers, cookies, passwords, tokens, and full request bodies. If needed, add redaction filters for known sensitive keys, but treat that as a safety layer, not the primary control.",[52,37809,37811],{"id":37810},"do-i-need-separate-logs-for-django-gunicorn-and-nginx","Do I need separate logs for Django, Gunicorn, and Nginx?",[16,37813,37814],{},"Yes. Django logs application behavior, Gunicorn logs worker and process behavior, and Nginx logs edge traffic and proxy failures. Keeping them separate makes troubleshooting much faster.",[52,37816,37818],{"id":37817},"how-do-i-rotate-django-logs-safely-on-linux","How do I rotate Django logs safely on Linux?",[16,37820,37821,37822,37824,37825,37827,37828,37831],{},"If Django writes to files, use ",[20,37823,36997],{}," with ",[20,37826,36906],{},", verify ownership and file creation mode, and test the config with ",[20,37829,37830],{},"logrotate -d",". If possible, prefer journald or container logging instead of app-managed files on newer deployments.",[1485,37833,37834],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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}",{"title":111,"searchDepth":149,"depth":149,"links":37836},[37837,37838,37839,37861,37862,37865,37866],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":37840},[37841,37846,37847,37848,37849,37850,37855],{"id":35720,"depth":149,"text":35721,"children":37842},[37843,37844,37845],{"id":35727,"depth":162,"text":35728},{"id":35747,"depth":162,"text":35748},{"id":35767,"depth":162,"text":35768},{"id":35789,"depth":149,"text":35790},{"id":36508,"depth":149,"text":36509},{"id":36856,"depth":149,"text":36857},{"id":37070,"depth":149,"text":37071},{"id":37420,"depth":149,"text":37421,"children":37851},[37852,37853,37854],{"id":37427,"depth":162,"text":37428},{"id":37445,"depth":162,"text":37446},{"id":37463,"depth":162,"text":37464},{"id":37547,"depth":149,"text":37548,"children":37856},[37857,37858,37859,37860],{"id":37554,"depth":162,"text":37555},{"id":37576,"depth":162,"text":37577},{"id":37639,"depth":162,"text":37640},{"id":37663,"depth":162,"text":37664},{"id":1320,"depth":136,"text":1321},{"id":30531,"depth":136,"text":30532,"children":37863},[37864],{"id":37736,"depth":149,"text":37737},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":37867},[37868,37869,37870,37871,37872],{"id":37777,"depth":149,"text":37778},{"id":37784,"depth":149,"text":37785},{"id":37803,"depth":149,"text":37804},{"id":37810,"depth":149,"text":37811},{"id":37817,"depth":149,"text":37818},{},"\u002Fdjango-logging-setup-production",[1552,37876,37877],"\u002Foptimize\u002Fzero-downtime-django-deployment","\u002Foptimize\u002Foptimize-gunicorn-workers-django",{"title":35639,"description":35646},[1557,14954],"django-logging-setup-production",[1557,14954],"OURFfw4RUksBtTdrvNHc6xqaSxg_0M2ND184yTQgj58",{"id":37884,"title":37885,"body":37886,"category":4543,"description":39188,"difficulty":23035,"extension":1544,"funnel_stage":1545,"intent":4546,"meta":39189,"navigation":309,"path":39190,"priority":24939,"related":39191,"role":4552,"section":4553,"seo":39192,"stack":39193,"stem":39194,"tags":39195,"type":4558,"__hash__":39196},"articles\u002Ffix-django-migration-failed-during-deploy.md","Django Migration Failed During Deploy: Recovery Playbook",{"type":8,"value":37887,"toc":39157},[37888,37890,37893,37907,37917,37919,37922,37972,37974,37978,37981,37995,37998,38001,38103,38105,38127,38130,38133,38163,38166,38193,38195,38240,38243,38247,38250,38253,38265,38268,38288,38291,38304,38307,38352,38355,38358,38409,38412,38445,38448,38492,38499,38502,38513,38516,38520,38523,38528,38542,38547,38563,38568,38582,38588,38592,38595,38609,38612,38615,38632,38635,38638,38655,38658,38681,38684,38699,38702,38705,38716,38719,38723,38726,38740,38743,38746,38754,38757,38782,38788,38791,38808,38811,38815,38818,38829,38832,38846,38849,38868,38875,38878,38903,38906,38909,38923,38925,38928,38944,38947,38950,38953,39002,39010,39012,39015,39017,39021,39024,39028,39031,39035,39041,39045,39048,39050,39056,39062,39067,39072,39077,39079,39083,39097,39104,39107,39111,39114,39122,39128,39132,39134,39154],[11,37889,14],{"id":13},[16,37891,37892],{},"A failed Django migration during deploy usually means your release reached production, but the database change step did not finish cleanly. That creates one of the highest-risk deployment states:",[63,37894,37895,37898,37901,37904],{},[66,37896,37897],{},"new app code may expect schema changes that are not present",[66,37899,37900],{},"part of a migration may have run, especially with non-transactional DDL or custom SQL",[66,37902,37903],{},"new web or worker processes may fail on startup",[66,37905,37906],{},"rolling deploys may leave old and new code hitting the same database with different assumptions",[16,37908,37909,37910,37912,37913,37916],{},"The main goal is not to make ",[20,37911,10296],{}," pass again as quickly as possible. The goal is to ",[1226,37914,37915],{},"contain the blast radius, determine the real database state, and choose the safest recovery path",": fix forward, roll back code, or restore and repair the database.",[11,37918,30],{"id":29},[16,37920,37921],{},"When a Django migration fails in production during deploy:",[1173,37923,37924,37929,37934,37939,37944,37967],{},[66,37925,37926],{},[1226,37927,37928],{},"Stop further deploys and retries",[66,37930,37931],{},[1226,37932,37933],{},"Pause workers and prevent new instances from starting incompatible code",[66,37935,37936],{},[1226,37937,37938],{},"Capture the exact failed migration name and error",[66,37940,37941],{},[1226,37942,37943],{},"Check both Django migration history and the real database schema and data state",[66,37945,37946,37947],{},"Decide whether to:\n",[63,37948,37949,37955,37961],{},[66,37950,37951,37954],{},[1226,37952,37953],{},"retry or fix forward"," if the failure is understood and safe",[66,37956,37957,37960],{},[1226,37958,37959],{},"roll back app code"," if the migration never changed the database and the previous release is still compatible",[66,37962,37963,37966],{},[1226,37964,37965],{},"restore or manually repair the database"," only when partial changes or data integrity issues make forward recovery unsafe",[66,37968,37969],{},[1226,37970,37971],{},"Verify app health, migration state, and background jobs before reopening traffic",[11,37973,43],{"id":42},[52,37975,37977],{"id":37976},"_1-first-contain-the-failed-deploy","1) First contain the failed deploy",[16,37979,37980],{},"Freeze the release pipeline first. Do not let automation make the incident worse.",[63,37982,37983,37986,37989,37992],{},[66,37984,37985],{},"disable auto-deploys in CI\u002FCD",[66,37987,37988],{},"stop rollout of new web instances",[66,37990,37991],{},"pause Celery workers, cron jobs, or other background jobs that depend on the new schema",[66,37993,37994],{},"if your app runs migrations in startup scripts, stop repeated restarts and retries",[16,37996,37997],{},"If needed, enable maintenance mode or drain traffic at the load balancer.",[16,37999,38000],{},"Example Nginx maintenance block:",[106,38002,38004],{"className":2154,"code":38003,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002F {\n        return 503;\n    }\n\n    error_page 503 @maintenance;\n    location @maintenance {\n        root \u002Fvar\u002Fwww\u002Fhtml;\n        try_files \u002Fmaintenance.html =503;\n    }\n}\n",[20,38005,38006,38012,38020,38026,38030,38038,38047,38051,38055,38066,38075,38082,38095,38099],{"__ignoreMap":111},[115,38007,38008,38010],{"class":117,"line":118},[115,38009,2163],{"class":121},[115,38011,2166],{"class":125},[115,38013,38014,38016,38018],{"class":117,"line":136},[115,38015,2171],{"class":121},[115,38017,3808],{"class":202},[115,38019,3811],{"class":125},[115,38021,38022,38024],{"class":117,"line":149},[115,38023,2182],{"class":121},[115,38025,2185],{"class":125},[115,38027,38028],{"class":117,"line":162},[115,38029,310],{"emptyLinePlaceholder":309},[115,38031,38032,38034,38036],{"class":117,"line":175},[115,38033,2214],{"class":121},[115,38035,2268],{"class":262},[115,38037,2220],{"class":125},[115,38039,38040,38042,38045],{"class":117,"line":350},[115,38041,6812],{"class":121},[115,38043,38044],{"class":202}," 503",[115,38046,3811],{"class":125},[115,38048,38049],{"class":117,"line":365},[115,38050,2233],{"class":125},[115,38052,38053],{"class":117,"line":380},[115,38054,310],{"emptyLinePlaceholder":309},[115,38056,38057,38060,38063],{"class":117,"line":487},[115,38058,38059],{"class":121},"    error_page ",[115,38061,38062],{"class":202},"503",[115,38064,38065],{"class":125}," @maintenance;\n",[115,38067,38068,38070,38073],{"class":117,"line":2095},[115,38069,2214],{"class":121},[115,38071,38072],{"class":262}," @maintenance ",[115,38074,2220],{"class":125},[115,38076,38077,38079],{"class":117,"line":2104},[115,38078,6788],{"class":121},[115,38080,38081],{"class":125},"\u002Fvar\u002Fwww\u002Fhtml;\n",[115,38083,38084,38087,38090,38093],{"class":117,"line":2113},[115,38085,38086],{"class":121},"        try_files ",[115,38088,38089],{"class":125},"\u002Fmaintenance.html ",[115,38091,38092],{"class":202},"=503",[115,38094,3811],{"class":125},[115,38096,38097],{"class":117,"line":2122},[115,38098,2233],{"class":125},[115,38100,38101],{"class":117,"line":2131},[115,38102,2323],{"class":125},[16,38104,24117],{},[106,38106,38107],{"className":108,"code":3897,"language":110,"meta":111,"style":111},[20,38108,38109],{"__ignoreMap":111},[115,38110,38111,38113,38115,38117,38119,38121,38123,38125],{"class":117,"line":118},[115,38112,2001],{"class":262},[115,38114,3906],{"class":132},[115,38116,3909],{"class":202},[115,38118,3912],{"class":125},[115,38120,2001],{"class":262},[115,38122,3480],{"class":132},[115,38124,3919],{"class":132},[115,38126,1996],{"class":132},[16,38128,38129],{},"If production serves HTTPS, apply the same maintenance response on the TLS server block as well. Do not update only the port 80 vhost while HTTPS traffic still reaches the app.",[16,38131,38132],{},"Record the incident context:",[63,38134,38135,38138,38141,38144],{},[66,38136,38137],{},"release version or commit SHA",[66,38139,38140],{},"failed migration name",[66,38142,38143],{},"exact error message and traceback",[66,38145,38146,38147],{},"where it failed:\n",[63,38148,38149,38152,38157,38160],{},[66,38150,38151],{},"CI\u002FCD release step",[66,38153,38154],{},[20,38155,38156],{},"python manage.py migrate",[66,38158,38159],{},"container entrypoint",[66,38161,38162],{},"app startup hook",[16,38164,38165],{},"Check runtime evidence:",[106,38167,38169],{"className":108,"code":38168,"language":110,"meta":111,"style":111},"systemctl status gunicorn\njournalctl -u gunicorn -n 200 --no-pager\n",[20,38170,38171,38179],{"__ignoreMap":111},[115,38172,38173,38175,38177],{"class":117,"line":118},[115,38174,1981],{"class":262},[115,38176,1984],{"class":132},[115,38178,1987],{"class":132},[115,38180,38181,38183,38185,38187,38189,38191],{"class":117,"line":136},[115,38182,2785],{"class":262},[115,38184,2788],{"class":202},[115,38186,2791],{"class":132},[115,38188,2794],{"class":202},[115,38190,31219],{"class":202},[115,38192,2800],{"class":202},[16,38194,33561],{},[106,38196,38198],{"className":108,"code":38197,"language":110,"meta":111,"style":111},"docker logs \u003Cweb-container> --tail 200\ndocker logs \u003Crelease-job-container> --tail 200\n",[20,38199,38200,38221],{"__ignoreMap":111},[115,38201,38202,38204,38206,38208,38211,38213,38215,38218],{"class":117,"line":118},[115,38203,3295],{"class":262},[115,38205,3301],{"class":132},[115,38207,7691],{"class":121},[115,38209,38210],{"class":132},"web-containe",[115,38212,31278],{"class":125},[115,38214,22818],{"class":121},[115,38216,38217],{"class":202}," --tail",[115,38219,38220],{"class":202}," 200\n",[115,38222,38223,38225,38227,38229,38232,38234,38236,38238],{"class":117,"line":136},[115,38224,3295],{"class":262},[115,38226,3301],{"class":132},[115,38228,7691],{"class":121},[115,38230,38231],{"class":132},"release-job-containe",[115,38233,31278],{"class":125},[115,38235,22818],{"class":121},[115,38237,38217],{"class":202},[115,38239,38220],{"class":202},[16,38241,38242],{},"If your services are in a crash loop, stop the loop before analysis. Repeated startup attempts can keep rerunning the same failing migration or keep starting incompatible code against a half-changed schema.",[52,38244,38246],{"id":38245},"_2-determine-what-state-the-database-is-in","2) Determine what state the database is in",[16,38248,38249],{},"Do not trust only the deploy output. Check Django’s view of migration history and the actual database state.",[16,38251,38252],{},"Show migrations:",[106,38254,38255],{"className":108,"code":11330,"language":110,"meta":111,"style":111},[20,38256,38257],{"__ignoreMap":111},[115,38258,38259,38261,38263],{"class":117,"line":118},[115,38260,1114],{"class":262},[115,38262,1117],{"class":132},[115,38264,1129],{"class":132},[16,38266,38267],{},"Inspect the SQL for the failed migration:",[106,38269,38271],{"className":108,"code":38270,"language":110,"meta":111,"style":111},"python manage.py sqlmigrate app_name 00xx_migration_name\n",[20,38272,38273],{"__ignoreMap":111},[115,38274,38275,38277,38279,38282,38285],{"class":117,"line":118},[115,38276,1114],{"class":262},[115,38278,1117],{"class":132},[115,38280,38281],{"class":132}," sqlmigrate",[115,38283,38284],{"class":132}," app_name",[115,38286,38287],{"class":132}," 00xx_migration_name\n",[16,38289,38290],{},"Open a DB shell from the same app environment:",[106,38292,38294],{"className":108,"code":38293,"language":110,"meta":111,"style":111},"python manage.py dbshell\n",[20,38295,38296],{"__ignoreMap":111},[115,38297,38298,38300,38302],{"class":117,"line":118},[115,38299,1114],{"class":262},[115,38301,1117],{"class":132},[115,38303,32717],{"class":132},[16,38305,38306],{},"Check whether Django marked the migration as applied:",[106,38308,38310],{"className":11064,"code":38309,"language":11066,"meta":111,"style":111},"SELECT app, name, applied\nFROM django_migrations\nORDER BY applied DESC\nLIMIT 20;\n",[20,38311,38312,38324,38331,38342],{"__ignoreMap":111},[115,38313,38314,38316,38319,38321],{"class":117,"line":118},[115,38315,11073],{"class":121},[115,38317,38318],{"class":125}," app, ",[115,38320,20820],{"class":121},[115,38322,38323],{"class":125},", applied\n",[115,38325,38326,38328],{"class":117,"line":136},[115,38327,11089],{"class":121},[115,38329,38330],{"class":125}," django_migrations\n",[115,38332,38333,38336,38339],{"class":117,"line":149},[115,38334,38335],{"class":121},"ORDER BY",[115,38337,38338],{"class":125}," applied ",[115,38340,38341],{"class":121},"DESC\n",[115,38343,38344,38347,38350],{"class":117,"line":162},[115,38345,38346],{"class":121},"LIMIT",[115,38348,38349],{"class":202}," 20",[115,38351,3811],{"class":125},[16,38353,38354],{},"Then inspect the schema directly. PostgreSQL examples:",[16,38356,38357],{},"Check whether a column exists:",[106,38359,38361],{"className":11064,"code":38360,"language":11066,"meta":111,"style":111},"SELECT column_name\nFROM information_schema.columns\nWHERE table_name = 'my_table'\n  AND column_name = 'new_column';\n",[20,38362,38363,38370,38382,38394],{"__ignoreMap":111},[115,38364,38365,38367],{"class":117,"line":118},[115,38366,11073],{"class":121},[115,38368,38369],{"class":125}," column_name\n",[115,38371,38372,38374,38377,38379],{"class":117,"line":136},[115,38373,11089],{"class":121},[115,38375,38376],{"class":202}," information_schema",[115,38378,211],{"class":125},[115,38380,38381],{"class":202},"columns\n",[115,38383,38384,38386,38389,38391],{"class":117,"line":149},[115,38385,11097],{"class":121},[115,38387,38388],{"class":125}," table_name ",[115,38390,129],{"class":121},[115,38392,38393],{"class":132}," 'my_table'\n",[115,38395,38396,38399,38402,38404,38407],{"class":117,"line":162},[115,38397,38398],{"class":121},"  AND",[115,38400,38401],{"class":125}," column_name ",[115,38403,129],{"class":121},[115,38405,38406],{"class":132}," 'new_column'",[115,38408,3811],{"class":125},[16,38410,38411],{},"Check indexes:",[106,38413,38415],{"className":11064,"code":38414,"language":11066,"meta":111,"style":111},"SELECT indexname, indexdef\nFROM pg_indexes\nWHERE tablename = 'my_table';\n",[20,38416,38417,38424,38431],{"__ignoreMap":111},[115,38418,38419,38421],{"class":117,"line":118},[115,38420,11073],{"class":121},[115,38422,38423],{"class":125}," indexname, indexdef\n",[115,38425,38426,38428],{"class":117,"line":136},[115,38427,11089],{"class":121},[115,38429,38430],{"class":125}," pg_indexes\n",[115,38432,38433,38435,38438,38440,38443],{"class":117,"line":149},[115,38434,11097],{"class":121},[115,38436,38437],{"class":125}," tablename ",[115,38439,129],{"class":121},[115,38441,38442],{"class":132}," 'my_table'",[115,38444,3811],{"class":125},[16,38446,38447],{},"Check locks and blockers:",[106,38449,38451],{"className":11064,"code":38450,"language":11066,"meta":111,"style":111},"SELECT pid, usename, state, wait_event_type, wait_event, query\nFROM pg_stat_activity\nWHERE datname = current_database()\nORDER BY query_start;\n",[20,38452,38453,38466,38473,38485],{"__ignoreMap":111},[115,38454,38455,38457,38460,38463],{"class":117,"line":118},[115,38456,11073],{"class":121},[115,38458,38459],{"class":125}," pid, usename, ",[115,38461,38462],{"class":121},"state",[115,38464,38465],{"class":125},", wait_event_type, wait_event, query\n",[115,38467,38468,38470],{"class":117,"line":136},[115,38469,11089],{"class":121},[115,38471,38472],{"class":125}," pg_stat_activity\n",[115,38474,38475,38477,38480,38482],{"class":117,"line":149},[115,38476,11097],{"class":121},[115,38478,38479],{"class":125}," datname ",[115,38481,129],{"class":121},[115,38483,38484],{"class":125}," current_database()\n",[115,38486,38487,38489],{"class":117,"line":162},[115,38488,38335],{"class":121},[115,38490,38491],{"class":125}," query_start;\n",[16,38493,38494,38495,38498],{},"For data migrations, inspect whether rows were already changed. If the migration contains ",[20,38496,38497],{},"RunPython"," or raw SQL, determine whether it is idempotent. A failed data migration may have modified some rows before crashing.",[16,38500,38501],{},"What you are trying to classify is one of these states:",[1173,38503,38504,38507,38510],{},[66,38505,38506],{},"migration not applied at all",[66,38508,38509],{},"migration partially applied",[66,38511,38512],{},"migration applied, but deploy failed later for another reason",[16,38514,38515],{},"Do not decide on rollback or retry until you know which state you are in.",[52,38517,38519],{"id":38518},"_3-classify-the-failure-before-taking-action","3) Classify the failure before taking action",[16,38521,38522],{},"Use the failure type to choose the path.",[16,38524,38525],{},[1226,38526,38527],{},"Usually safe to retry after containment:",[63,38529,38530,38533,38536,38539],{},[66,38531,38532],{},"temporary database connectivity loss",[66,38534,38535],{},"lock timeout",[66,38537,38538],{},"interrupted release job",[66,38540,38541],{},"transient environment issue unrelated to schema change logic",[16,38543,38544],{},[1226,38545,38546],{},"Requires code or migration fix:",[63,38548,38549,38554,38557,38560],{},[66,38550,38551,38552],{},"broken ",[20,38553,38497],{},[66,38555,38556],{},"invalid SQL in migration",[66,38558,38559],{},"missing import or dependency",[66,38561,38562],{},"cross-branch migration ordering problem",[16,38564,38565],{},[1226,38566,38567],{},"Higher-risk database compatibility or scale issue:",[63,38569,38570,38573,38576,38579],{},[66,38571,38572],{},"unique constraint creation blocked by duplicate rows",[66,38574,38575],{},"large table rewrite taking too long",[66,38577,38578],{},"insufficient privileges for DDL",[66,38580,38581],{},"partially applied non-transactional changes",[16,38583,38584,38585,211],{},"If you cannot clearly classify the failure, assume it is ",[1226,38586,38587],{},"not safe to blindly rerun",[52,38589,38591],{"id":38590},"_4-recovery-path-1-fix-forward-and-complete-the-migration","4) Recovery path 1: fix forward and complete the migration",[16,38593,38594],{},"Choose fix-forward when:",[63,38596,38597,38600,38603,38606],{},[66,38598,38599],{},"the previous app release is no longer schema-compatible",[66,38601,38602],{},"the failure is understood",[66,38604,38605],{},"the database is repairable without restore",[66,38607,38608],{},"schema or data integrity can be preserved",[16,38610,38611],{},"Before retrying, confirm you have a backup or snapshot. In production, verify it explicitly rather than assuming one exists.",[16,38613,38614],{},"Run migrations manually, outside app startup:",[106,38616,38618],{"className":108,"code":38617,"language":110,"meta":111,"style":111},"python manage.py migrate app_name 00xx_migration_name\n",[20,38619,38620],{"__ignoreMap":111},[115,38621,38622,38624,38626,38628,38630],{"class":117,"line":118},[115,38623,1114],{"class":262},[115,38625,1117],{"class":132},[115,38627,1826],{"class":132},[115,38629,38284],{"class":132},[115,38631,38287],{"class":132},[16,38633,38634],{},"If needed, ship a targeted fix and redeploy only the release step first. Keep old and new code compatible during the transition.",[16,38636,38637],{},"A safer release sequence is:",[1173,38639,38640,38643,38646,38649,38652],{},[66,38641,38642],{},"deploy fixed code or migration",[66,38644,38645],{},"run migration as a dedicated one-off step",[66,38647,38648],{},"verify migration state",[66,38650,38651],{},"restart web",[66,38653,38654],{},"restart workers",[16,38656,38657],{},"Sanity checks after fix-forward:",[106,38659,38661],{"className":108,"code":38660,"language":110,"meta":111,"style":111},"python manage.py showmigrations\npython manage.py check --deploy\n",[20,38662,38663,38671],{"__ignoreMap":111},[115,38664,38665,38667,38669],{"class":117,"line":118},[115,38666,1114],{"class":262},[115,38668,1117],{"class":132},[115,38670,1129],{"class":132},[115,38672,38673,38675,38677,38679],{"class":117,"line":136},[115,38674,1114],{"class":262},[115,38676,1117],{"class":132},[115,38678,1814],{"class":132},[115,38680,1817],{"class":202},[16,38682,38683],{},"Inspect whether Django still plans to apply any migrations:",[106,38685,38687],{"className":108,"code":38686,"language":110,"meta":111,"style":111},"python manage.py migrate --plan\n",[20,38688,38689],{"__ignoreMap":111},[115,38690,38691,38693,38695,38697],{"class":117,"line":118},[115,38692,1114],{"class":262},[115,38694,1117],{"class":132},[115,38696,1826],{"class":132},[115,38698,1829],{"class":202},[16,38700,38701],{},"Then test app health endpoints, admin access, and a schema-sensitive path.",[16,38703,38704],{},"If the migration was data-heavy, also check:",[63,38706,38707,38710,38713],{},[66,38708,38709],{},"row counts or spot-checks for affected records",[66,38711,38712],{},"worker queues for jobs that may replay bad assumptions",[66,38714,38715],{},"database locks, long-running transactions, or blocked sessions",[16,38717,38718],{},"Rollback note: if the fixed migration still fails and the database state is getting more complex, stop and reassess. Do not keep retrying different ad hoc changes against production.",[52,38720,38722],{"id":38721},"_5-recovery-path-2-roll-back-application-code-safely","5) Recovery path 2: roll back application code safely",[16,38724,38725],{},"Roll back code when:",[63,38727,38728,38734,38737],{},[66,38729,38730,38731,38733],{},"the migration did ",[1226,38732,7474],{}," change the database",[66,38735,38736],{},"the failed step happened before schema changes",[66,38738,38739],{},"the old release remains compatible with the current database",[16,38741,38742],{},"Redeploy the last known good app version. Roll back workers and scheduled jobs too, not just the web service.",[16,38744,38745],{},"Examples:",[63,38747,38748,38751],{},[66,38749,38750],{},"VM or systemd deployment: switch the app directory, artifact, or release symlink back to the previous known-good version, then restart services",[66,38752,38753],{},"containerized deployment: redeploy the previous image tag for both web and worker services",[16,38755,38756],{},"After redeploying the last known good release, restart the affected services:",[106,38758,38760],{"className":108,"code":38759,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn\nsudo systemctl restart celery\n",[20,38761,38762,38772],{"__ignoreMap":111},[115,38763,38764,38766,38768,38770],{"class":117,"line":118},[115,38765,2001],{"class":262},[115,38767,3480],{"class":132},[115,38769,3483],{"class":132},[115,38771,1987],{"class":132},[115,38773,38774,38776,38778,38780],{"class":117,"line":136},[115,38775,2001],{"class":262},[115,38777,3480],{"class":132},[115,38779,3483],{"class":132},[115,38781,4703],{"class":132},[16,38783,38784,38785,38787],{},"Important: do ",[1226,38786,7474],{}," automatically reverse migrations in production unless you have tested that reversal and know it is safe. Many reverse operations are risky, slow, or lossy.",[16,38789,38790],{},"Verification after code rollback:",[63,38792,38793,38796,38799,38802,38805],{},[66,38794,38795],{},"app starts without model or schema mismatch errors",[66,38797,38798],{},"requests succeed",[66,38800,38801],{},"background jobs run normally",[66,38803,38804],{},"no startup script is still trying to apply the broken migration",[66,38806,38807],{},"the rolled-back release is actually the version now running",[16,38809,38810],{},"A rollback is only successful if both the code version and the running processes match the intended release.",[52,38812,38814],{"id":38813},"_6-recovery-path-3-restore-or-manually-repair-the-database","6) Recovery path 3: restore or manually repair the database",[16,38816,38817],{},"Use restore or manual repair only when:",[63,38819,38820,38823,38826],{},[66,38821,38822],{},"partial schema changes were applied and forward recovery is unsafe",[66,38824,38825],{},"a data migration half-finished and damaged consistency",[66,38827,38828],{},"non-transactional operations left the schema in a broken intermediate state",[16,38830,38831],{},"Restore considerations:",[63,38833,38834,38837,38840,38843],{},[66,38835,38836],{},"use point-in-time recovery or a tested snapshot process",[66,38838,38839],{},"define the expected data loss window before acting",[66,38841,38842],{},"coordinate app rollback with DB restore so code and schema match",[66,38844,38845],{},"restrict DB credentials and shell access during emergency operations",[16,38847,38848],{},"Manual repair may include:",[63,38850,38851,38854,38857,38860],{},[66,38852,38853],{},"dropping a partially created index or constraint",[66,38855,38856],{},"reverting a failed column change",[66,38858,38859],{},"correcting rows affected by a partial data migration",[66,38861,38862,38863,2957,38865],{},"updating ",[20,38864,1466],{},[1226,38866,38867],{},"only if the schema truly matches the recorded state",[16,38869,38870,38871,38874],{},"Be careful with ",[20,38872,38873],{},"--fake",". This is only safe when the database already matches what Django expects.",[16,38876,38877],{},"Example of targeted inspection before any fake apply:",[106,38879,38881],{"className":108,"code":38880,"language":110,"meta":111,"style":111},"python manage.py sqlmigrate app_name 00xx_migration_name\npython manage.py dbshell\n",[20,38882,38883,38895],{"__ignoreMap":111},[115,38884,38885,38887,38889,38891,38893],{"class":117,"line":118},[115,38886,1114],{"class":262},[115,38888,1117],{"class":132},[115,38890,38281],{"class":132},[115,38892,38284],{"class":132},[115,38894,38287],{"class":132},[115,38896,38897,38899,38901],{"class":117,"line":136},[115,38898,1114],{"class":262},[115,38900,1117],{"class":132},[115,38902,32717],{"class":132},[16,38904,38905],{},"If the SQL says a column should exist, verify it exists. If an index should exist, verify that exact index exists. Only then consider a fake mark, and only as part of a controlled repair.",[16,38907,38908],{},"If you need to restore, document:",[63,38910,38911,38914,38917,38920],{},[66,38912,38913],{},"recovery point used",[66,38915,38916],{},"expected data loss window",[66,38918,38919],{},"code version paired with the restored database",[66,38921,38922],{},"follow-up actions needed before reopening traffic",[11,38924,1321],{"id":1320},[16,38926,38927],{},"This recovery flow works because it separates three states that often get mixed together:",[1173,38929,38930,38935,38939],{},[66,38931,38932],{},[1226,38933,38934],{},"migration not applied",[66,38936,38937],{},[1226,38938,38509],{},[66,38940,38941],{},[1226,38942,38943],{},"migration applied, but release still failed for another reason",[16,38945,38946],{},"That distinction determines whether rollback is easy, dangerous, or impossible.",[16,38948,38949],{},"A dedicated migration step is also the main prevention measure. Running migrations from app startup is convenient early on, but in production it causes repeated retries, race conditions, and multiple instances competing to run the same change. A single release job is much safer.",[16,38951,38952],{},"Example CI\u002FCD pattern:",[106,38954,38956],{"className":108,"code":38955,"language":110,"meta":111,"style":111},"# release step\npython manage.py migrate --noinput\n\n# only if migrate succeeds\nsudo systemctl restart gunicorn\nsudo systemctl restart celery\n",[20,38957,38958,38963,38973,38977,38982,38992],{"__ignoreMap":111},[115,38959,38960],{"class":117,"line":118},[115,38961,38962],{"class":3861},"# release step\n",[115,38964,38965,38967,38969,38971],{"class":117,"line":136},[115,38966,1114],{"class":262},[115,38968,1117],{"class":132},[115,38970,1826],{"class":132},[115,38972,1841],{"class":202},[115,38974,38975],{"class":117,"line":149},[115,38976,310],{"emptyLinePlaceholder":309},[115,38978,38979],{"class":117,"line":162},[115,38980,38981],{"class":3861},"# only if migrate succeeds\n",[115,38983,38984,38986,38988,38990],{"class":117,"line":175},[115,38985,2001],{"class":262},[115,38987,3480],{"class":132},[115,38989,3483],{"class":132},[115,38991,1987],{"class":132},[115,38993,38994,38996,38998,39000],{"class":117,"line":350},[115,38995,2001],{"class":262},[115,38997,3480],{"class":132},[115,38999,3483],{"class":132},[115,39001,4703],{"class":132},[16,39003,39004,39005,1153,39007,39009],{},"Keep secrets out of command history and repo files. Use the same production environment variables you normally use for app runtime, such as ",[20,39006,10873],{},[20,39008,5074],{},", and secret values loaded from your process manager, secret store, or container environment. Do not paste credentials into ad hoc shell commands unless there is no safer option.",[52,39011,4319],{"id":4318},[16,39013,39014],{},"If your team repeats these incident checks often, standardize them. Common candidates are backup verification, single migration runner enforcement, collection of migration and log evidence, maintenance-mode toggles, and post-recovery health checks. A reusable release template or incident script reduces decision errors during a real outage.",[11,39016,1337],{"id":1336},[52,39018,39020],{"id":39019},"failed-migration-during-rolling-deploys","Failed migration during rolling deploys",[16,39022,39023],{},"This is one of the worst cases. Old and new app versions may both hit one database. If the new version expects a column that does not exist yet, only part of the fleet may fail. Drain traffic, stop the rollout, and make sure workers are version-aligned with the chosen recovery path.",[52,39025,39027],{"id":39026},"failed-data-migration-on-a-large-table","Failed data migration on a large table",[16,39029,39030],{},"Large updates can hold locks or run long enough to exceed deploy timeouts. If a data migration failed, inspect how many rows changed before retrying. Batch updates and idempotent logic are safer than one large write.",[52,39032,39034],{"id":39033},"multiple-containers-retried-the-same-migration","Multiple containers retried the same migration",[16,39036,39037,39038,39040],{},"If every container runs ",[20,39039,10296],{}," on startup, one failure can cascade into repeated attempts. Move migration execution into a dedicated release job and ensure only one runner is active at a time.",[52,39042,39044],{"id":39043},"postgresql-transactional-behavior","PostgreSQL transactional behavior",[16,39046,39047],{},"Many PostgreSQL schema changes are transactional, but not all operational patterns are harmless. Custom SQL, concurrent index creation strategies, and app-level data changes still need explicit review. Check the actual migration SQL and the actual database state rather than assuming rollback behavior.",[11,39049,1386],{"id":1385},[16,39051,39052,39053,211],{},"For the background on migration state and compatibility, see ",[1395,39054,39055],{"href":1409},"How Django Migrations Work in Production",[16,39057,39058,39059,211],{},"For a safer release design, read ",[1395,39060,39061],{"href":1409},"How to Run Django Migrations Safely During Deployment",[16,39063,39064,39065,211],{},"If you need the full web stack around the app server, see ",[1395,39066,4425],{"href":2985},[16,39068,39069,39070,211],{},"For app version rollback steps, see ",[1395,39071,17929],{"href":1415},[16,39073,39074,39075,211],{},"If the failed deploy exposed broader production gaps, review ",[1395,39076,3000],{"href":2999},[11,39078,1420],{"id":1419},[52,39080,39082],{"id":39081},"how-do-i-know-whether-a-failed-django-migration-changed-the-database","How do I know whether a failed Django migration changed the database?",[16,39084,39085,39086,39088,39089,39092,39093,39096],{},"Check both ",[20,39087,1466],{}," and the real database schema. Use ",[20,39090,39091],{},"showmigrations",", inspect the migration SQL with ",[20,39094,39095],{},"sqlmigrate",", and then verify tables, columns, indexes, constraints, and any affected rows directly in the database.",[52,39098,39100,39101,39103],{"id":39099},"is-it-safe-to-rerun-python-managepy-migrate-after-a-deployment-failure","Is it safe to rerun ",[20,39102,38156],{}," after a deployment failure?",[16,39105,39106],{},"Sometimes, but only after you confirm why it failed. Retrying is often reasonable for transient failures like lock timeouts or interrupted release jobs. It is not safe to blindly rerun if the migration contains broken logic, partial data changes, or non-transactional schema changes.",[52,39108,39110],{"id":39109},"when-should-i-roll-back-code-instead-of-fixing-forward","When should I roll back code instead of fixing forward?",[16,39112,39113],{},"Roll back code when the migration never changed the database and the previous release is still compatible with the current schema. If schema changes already landed or the old code cannot work with the current database state, fix-forward is usually safer.",[52,39115,39117,39118,39121],{"id":39116},"can-i-use-python-managepy-migrate-fake-in-production","Can I use ",[20,39119,39120],{},"python manage.py migrate --fake"," in production?",[16,39123,39124,39125,39127],{},"Only when you have verified that the database already matches the migration’s intended state. ",[20,39126,38873],{}," is not a repair tool by itself. If you fake-apply a migration against the wrong schema, you can make later deploys much harder to recover.",[52,39129,39131],{"id":39130},"what-should-i-check-before-reopening-traffic-after-a-migration-incident","What should I check before reopening traffic after a migration incident?",[16,39133,22752],{},[63,39135,39136,39139,39142,39145,39148,39151],{},[66,39137,39138],{},"migration history is correct",[66,39140,39141],{},"the actual schema matches expected state",[66,39143,39144],{},"web health checks pass",[66,39146,39147],{},"workers are running the correct version",[66,39149,39150],{},"no background job is still using old assumptions",[66,39152,39153],{},"critical user paths and admin actions succeed",[1485,39155,39156],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":39158},[39159,39160,39161,39169,39172,39178,39179],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":39162},[39163,39164,39165,39166,39167,39168],{"id":37976,"depth":149,"text":37977},{"id":38245,"depth":149,"text":38246},{"id":38518,"depth":149,"text":38519},{"id":38590,"depth":149,"text":38591},{"id":38721,"depth":149,"text":38722},{"id":38813,"depth":149,"text":38814},{"id":1320,"depth":136,"text":1321,"children":39170},[39171],{"id":4318,"depth":149,"text":4319},{"id":1336,"depth":136,"text":1337,"children":39173},[39174,39175,39176,39177],{"id":39019,"depth":149,"text":39020},{"id":39026,"depth":149,"text":39027},{"id":39033,"depth":149,"text":39034},{"id":39043,"depth":149,"text":39044},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":39180},[39181,39182,39184,39185,39187],{"id":39081,"depth":149,"text":39082},{"id":39099,"depth":149,"text":39183},"Is it safe to rerun python manage.py migrate after a deployment failure?",{"id":39109,"depth":149,"text":39110},{"id":39116,"depth":149,"text":39186},"Can I use python manage.py migrate --fake in production?",{"id":39130,"depth":149,"text":39131},"A failed Django migration during deploy usually means your release reached production, but the database change step did not finish cleanly.",{},"\u002Ffix-django-migration-failed-during-deploy",[6333,4455,4445],{"title":37885,"description":39188},[1557,1558],"fix-django-migration-failed-during-deploy",[1557,1558],"9wo2i0DyqeHOQ0rdhALKgRIslrpt5VkiMBqXHv4EqQI",{"id":39198,"title":39199,"body":39200,"category":1541,"description":39206,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":40220,"navigation":309,"path":40221,"priority":3351,"related":40222,"role":34162,"section":1554,"seo":40224,"stack":40225,"stem":40226,"tags":40227,"type":3104,"__hash__":40228},"articles\u002Fdjango-migrations-deployment-strategy.md","Django Migration Strategy for Safe Deployments",{"type":8,"value":39201,"toc":40173},[39202,39204,39207,39213,39233,39236,39238,39241,39267,39270,39272,39276,39279,39283,39286,39300,39304,39307,39321,39325,39328,39345,39348,39375,39378,39396,39398,39409,39413,39416,39420,39423,39443,39446,39476,39479,39483,39486,39488,39499,39502,39506,39509,39513,39516,39539,39542,39599,39602,39608,39652,39655,39659,39662,39665,39679,39682,39686,39689,39703,39706,39709,39723,39726,39730,39733,39736,39754,39757,39761,39765,39768,39805,39807,39821,39825,39831,39868,39870,39884,39888,39891,39893,39907,39910,39958,39961,39965,39968,39972,39986,39989,40006,40009,40013,40016,40027,40030,40032,40035,40049,40052,40055,40059,40062,40064,40068,40071,40075,40078,40089,40093,40096,40100,40103,40105,40110,40115,40122,40129,40131,40135,40138,40146,40149,40153,40156,40160,40163,40167,40170],[11,39203,14],{"id":13},[16,39205,39206],{},"A good app deploy can still fail if the database migration step is unsafe. That is the real problem behind many Django migration deployment issues in production.",[16,39208,39209,39210,39212],{},"In development, ",[20,39211,38156],{}," is usually enough. In production, it is not. The risk comes from timing and compatibility:",[63,39214,39215,39218,39221,39224,39227,39230],{},[66,39216,39217],{},"new app code may expect columns or tables that do not exist yet",[66,39219,39220],{},"old app code may break if constraints, renames, or column removals are applied too early",[66,39222,39223],{},"a migration may lock a large table longer than expected",[66,39225,39226],{},"a data migration may turn a quick release into a long-running maintenance event",[66,39228,39229],{},"multiple app instances may start against different schema versions during rollout",[66,39231,39232],{},"rollback is often easy for app code but hard for database changes",[16,39234,39235],{},"That is why a Django database migration deployment process needs more than just a command. You need a release order, a compatibility strategy, verification checks, and a recovery plan before the migration runs.",[11,39237,30],{"id":29},[16,39239,39240],{},"The safest default approach for safe Django migrations in production is:",[1173,39242,39243,39246,39249,39252,39255,39258,39261,39264],{},[66,39244,39245],{},"Review the migration and classify its risk.",[66,39247,39248],{},"Make schema changes backward-compatible first.",[66,39250,39251],{},"Take a fresh backup or confirm a recent snapshot exists.",[66,39253,39254],{},"Run backward-compatible migrations once in a controlled deployment step.",[66,39256,39257],{},"Restart or roll the app only after migrations succeed.",[66,39259,39260],{},"For incompatible schema changes, split the change across multiple releases instead of deploying code and schema together.",[66,39262,39263],{},"Verify health checks, logs, and database state.",[66,39265,39266],{},"Use expand-and-contract for renames, drops, and large data changes.",[16,39268,39269],{},"This default is usually enough for small to medium Django apps using PostgreSQL, whether you deploy by SSH, systemd, or CI\u002FCD, as long as one migration runner is responsible for the schema change.",[11,39271,43],{"id":42},[11,39273,39275],{"id":39274},"step-1-classify-the-migration-before-deployment","Step 1: Classify the migration before deployment",[16,39277,39278],{},"Not all migrations carry the same production risk.",[52,39280,39282],{"id":39281},"low-risk-migrations","Low-risk migrations",[16,39284,39285],{},"These are usually easier to ship:",[63,39287,39288,39291,39294,39297],{},[66,39289,39290],{},"creating a new table",[66,39292,39293],{},"adding a nullable column",[66,39295,39296],{},"adding indexes using an approach your database supports safely",[66,39298,39299],{},"simple metadata changes with little or no table rewrite risk",[52,39301,39303],{"id":39302},"medium-risk-migrations","Medium-risk migrations",[16,39305,39306],{},"These need more review:",[63,39308,39309,39312,39315,39318],{},[66,39310,39311],{},"adding non-null constraints",[66,39313,39314],{},"adding defaults on large tables",[66,39316,39317],{},"foreign keys on large existing tables",[66,39319,39320],{},"data migrations that update many rows",[52,39322,39324],{"id":39323},"high-risk-migrations","High-risk migrations",[16,39326,39327],{},"These should trigger extra planning:",[63,39329,39330,39333,39336,39339,39342],{},[66,39331,39332],{},"dropping columns",[66,39334,39335],{},"renaming columns used by live code",[66,39337,39338],{},"changing types on large tables",[66,39340,39341],{},"large backfills inside a single migration",[66,39343,39344],{},"anything irreversible",[16,39346,39347],{},"Before release, run:",[106,39349,39351],{"className":108,"code":39350,"language":110,"meta":111,"style":111},"python manage.py makemigrations --check --dry-run\npython manage.py showmigrations\n",[20,39352,39353,39367],{"__ignoreMap":111},[115,39354,39355,39357,39359,39362,39365],{"class":117,"line":118},[115,39356,1114],{"class":262},[115,39358,1117],{"class":132},[115,39360,39361],{"class":132}," makemigrations",[115,39363,39364],{"class":202}," --check",[115,39366,7819],{"class":202},[115,39368,39369,39371,39373],{"class":117,"line":136},[115,39370,1114],{"class":262},[115,39372,1117],{"class":132},[115,39374,1129],{"class":132},[16,39376,39377],{},"Preview the SQL for migrations that could lock or rewrite data:",[106,39379,39381],{"className":108,"code":39380,"language":110,"meta":111,"style":111},"python manage.py sqlmigrate app_name 000X\n",[20,39382,39383],{"__ignoreMap":111},[115,39384,39385,39387,39389,39391,39393],{"class":117,"line":118},[115,39386,1114],{"class":262},[115,39388,1117],{"class":132},[115,39390,38281],{"class":132},[115,39392,38284],{"class":132},[115,39394,39395],{"class":132}," 000X\n",[16,39397,3515],{},[63,39399,39400,39403,39406],{},[66,39401,39402],{},"confirm no missing migrations",[66,39404,39405],{},"confirm the migration list matches what you expect to release",[66,39407,39408],{},"review SQL for table locks, index creation, and large updates",[11,39410,39412],{"id":39411},"step-2-design-backward-compatible-migrations","Step 2: Design backward-compatible migrations",[16,39414,39415],{},"For a safe Django schema migration strategy in production, assume old and new app versions may overlap during deployment.",[52,39417,39419],{"id":39418},"expand-first-contract-later","Expand first, contract later",[16,39421,39422],{},"The safest pattern is:",[1173,39424,39425,39428,39431,39434,39437,39440],{},[66,39426,39427],{},"add new schema",[66,39429,39430],{},"deploy app code that works with both old and new schema",[66,39432,39433],{},"migrate reads and writes if needed",[66,39435,39436],{},"backfill data separately",[66,39438,39439],{},"enforce constraints later",[66,39441,39442],{},"remove old schema in a later release",[16,39444,39445],{},"Example of safe sequencing for a rename:",[63,39447,39448,39458,39461,39466,39471],{},[66,39449,39450,39451,39454,39455],{},"release 1: add ",[20,39452,39453],{},"new_field",", keep ",[20,39456,39457],{},"old_field",[66,39459,39460],{},"release 2: write to both or read from fallback logic",[66,39462,39463,39464],{},"release 3: backfill ",[20,39465,39453],{},[66,39467,39468,39469],{},"release 4: switch app fully to ",[20,39470,39453],{},[66,39472,39473,39474],{},"release 5: drop ",[20,39475,39457],{},[16,39477,39478],{},"This is the core of zero-downtime Django migrations. Django does not guarantee zero downtime by itself. Compatibility does.",[52,39480,39482],{"id":39481},"separate-schema-changes-from-data-backfills","Separate schema changes from data backfills",[16,39484,39485],{},"Do not turn one deploy into a schema change plus a massive update unless the dataset is small and runtime is predictable.",[16,39487,11125],{},[63,39489,39490,39493,39496],{},[66,39491,39492],{},"fast schema migration during deploy",[66,39494,39495],{},"batched or background backfill after deploy",[66,39497,39498],{},"final constraint enforcement later",[16,39500,39501],{},"This reduces lock time and rollback pressure.",[11,39503,39505],{"id":39504},"step-3-define-the-production-release-order","Step 3: Define the production release order",[16,39507,39508],{},"The exact order depends on whether the migration is backward-compatible.",[52,39510,39512],{"id":39511},"for-backward-compatible-migrations","For backward-compatible migrations",[16,39514,39515],{},"A practical order for systemd or SSH-based releases is:",[1173,39517,39518,39521,39524,39527,39530,39533,39536],{},[66,39519,39520],{},"put the new release on the server",[66,39522,39523],{},"run pre-deploy checks",[66,39525,39526],{},"confirm backup readiness",[66,39528,39529],{},"run migrations once",[66,39531,39532],{},"restart or roll the app",[66,39534,39535],{},"run health checks",[66,39537,39538],{},"review logs",[16,39540,39541],{},"Example commands:",[106,39543,39545],{"className":108,"code":39544,"language":110,"meta":111,"style":111},"python manage.py showmigrations\npython manage.py migrate --noinput\nsudo systemctl restart gunicorn\ncurl -f https:\u002F\u002Fexample.com\u002Fhealthz\nsudo journalctl -u gunicorn -n 100 --no-pager\n",[20,39546,39547,39555,39565,39575,39583],{"__ignoreMap":111},[115,39548,39549,39551,39553],{"class":117,"line":118},[115,39550,1114],{"class":262},[115,39552,1117],{"class":132},[115,39554,1129],{"class":132},[115,39556,39557,39559,39561,39563],{"class":117,"line":136},[115,39558,1114],{"class":262},[115,39560,1117],{"class":132},[115,39562,1826],{"class":132},[115,39564,1841],{"class":202},[115,39566,39567,39569,39571,39573],{"class":117,"line":149},[115,39568,2001],{"class":262},[115,39570,3480],{"class":132},[115,39572,3483],{"class":132},[115,39574,1987],{"class":132},[115,39576,39577,39579,39581],{"class":117,"line":162},[115,39578,2764],{"class":262},[115,39580,2777],{"class":202},[115,39582,2780],{"class":132},[115,39584,39585,39587,39589,39591,39593,39595,39597],{"class":117,"line":175},[115,39586,2001],{"class":262},[115,39588,5030],{"class":132},[115,39590,2788],{"class":202},[115,39592,2791],{"class":132},[115,39594,2794],{"class":202},[115,39596,2797],{"class":202},[115,39598,2800],{"class":202},[16,39600,39601],{},"Use the reload or restart method that matches your process manager and service configuration, and verify it is graceful under production traffic.",[16,39603,39604,39605,39607],{},"If you use PostgreSQL backups directly, use a ",[20,39606,22],{}," command that matches your environment variables or connection parameters:",[106,39609,39611],{"className":108,"code":39610,"language":110,"meta":111,"style":111},"pg_dump -Fc -d \"$PGDATABASE\" -h \"$PGHOST\" -U \"$PGUSER\" > predeploy.dump\n",[20,39612,39613],{"__ignoreMap":111},[115,39614,39615,39617,39620,39622,39624,39627,39629,39631,39633,39636,39638,39640,39642,39645,39647,39649],{"class":117,"line":118},[115,39616,22],{"class":262},[115,39618,39619],{"class":202}," -Fc",[115,39621,1019],{"class":202},[115,39623,325],{"class":132},[115,39625,39626],{"class":125},"$PGDATABASE",[115,39628,331],{"class":132},[115,39630,992],{"class":202},[115,39632,325],{"class":132},[115,39634,39635],{"class":125},"$PGHOST",[115,39637,331],{"class":132},[115,39639,1010],{"class":202},[115,39641,325],{"class":132},[115,39643,39644],{"class":125},"$PGUSER",[115,39646,331],{"class":132},[115,39648,604],{"class":121},[115,39650,39651],{"class":132}," predeploy.dump\n",[16,39653,39654],{},"If you use a managed database, a provider snapshot is often better than an ad hoc dump for recovery speed.",[52,39656,39658],{"id":39657},"for-incompatible-migrations","For incompatible migrations",[16,39660,39661],{},"Do not deploy incompatible code and schema changes in one release.",[16,39663,39664],{},"Instead, split the change across releases:",[1173,39666,39667,39670,39673,39676],{},[66,39668,39669],{},"release backward-compatible schema changes first",[66,39671,39672],{},"deploy code that can work with both schema versions",[66,39674,39675],{},"backfill or transition data if needed",[66,39677,39678],{},"enforce constraints, drop old columns, or remove compatibility code in a later release",[16,39680,39681],{},"This matters in rolling, blue-green, and multi-server deployments where old and new app versions may run at the same time.",[52,39683,39685],{"id":39684},"container-or-cicd-deployment-sequence","Container or CI\u002FCD deployment sequence",[16,39687,39688],{},"For container-based deploys, use a dedicated migration job:",[1173,39690,39691,39694,39697,39700],{},[66,39692,39693],{},"build image",[66,39695,39696],{},"run migration job with production credentials",[66,39698,39699],{},"block rollout if migration fails",[66,39701,39702],{},"deploy app containers only after success",[16,39704,39705],{},"Do not rely on every app container to run migrations at startup. That creates race conditions and makes failures harder to control.",[16,39707,39708],{},"Who should run migrations:",[63,39710,39711,39714,39717,39720],{},[66,39712,39713],{},"one host",[66,39715,39716],{},"one CI job",[66,39718,39719],{},"one release task",[66,39721,39722],{},"never every replica",[16,39724,39725],{},"A deployment lock in CI\u002FCD is useful so only one pipeline can apply schema changes at a time.",[52,39727,39729],{"id":39728},"secrets-and-environment-safety","Secrets and environment safety",[16,39731,39732],{},"Use production credentials only in the migration step that needs them. Keep them in your normal secret store or environment management system, not hard-coded in scripts.",[16,39734,39735],{},"Before running migrations, confirm the target database without printing secrets:",[106,39737,39739],{"className":108,"code":39738,"language":110,"meta":111,"style":111},"python manage.py shell -c \"from django.conf import settings; print(settings.DATABASES['default']['HOST'], settings.DATABASES['default']['NAME'])\"\n",[20,39740,39741],{"__ignoreMap":111},[115,39742,39743,39745,39747,39749,39751],{"class":117,"line":118},[115,39744,1114],{"class":262},[115,39746,1117],{"class":132},[115,39748,9071],{"class":132},[115,39750,1024],{"class":202},[115,39752,39753],{"class":132}," \"from django.conf import settings; print(settings.DATABASES['default']['HOST'], settings.DATABASES['default']['NAME'])\"\n",[16,39755,39756],{},"Production and staging mix-ups are avoidable if the release process confirms the target environment and database name without printing secrets, and requires a confirmation step in manual workflows.",[11,39758,39760],{"id":39759},"step-4-verify-before-and-after-the-migration","Step 4: Verify before and after the migration",[52,39762,39764],{"id":39763},"pre-deploy-checks","Pre-deploy checks",[16,39766,39767],{},"Use these before touching production:",[106,39769,39771],{"className":108,"code":39770,"language":110,"meta":111,"style":111},"python manage.py makemigrations --check --dry-run\npython manage.py showmigrations\npython manage.py sqlmigrate app_name 000X\n",[20,39772,39773,39785,39793],{"__ignoreMap":111},[115,39774,39775,39777,39779,39781,39783],{"class":117,"line":118},[115,39776,1114],{"class":262},[115,39778,1117],{"class":132},[115,39780,39361],{"class":132},[115,39782,39364],{"class":202},[115,39784,7819],{"class":202},[115,39786,39787,39789,39791],{"class":117,"line":136},[115,39788,1114],{"class":262},[115,39790,1117],{"class":132},[115,39792,1129],{"class":132},[115,39794,39795,39797,39799,39801,39803],{"class":117,"line":149},[115,39796,1114],{"class":262},[115,39798,1117],{"class":132},[115,39800,38281],{"class":132},[115,39802,38284],{"class":132},[115,39804,39395],{"class":132},[16,39806,1132],{},[63,39808,39809,39812,39815,39818],{},[66,39810,39811],{},"staging has already run the same migration",[66,39813,39814],{},"backup or snapshot is recent",[66,39816,39817],{},"expected migration runtime is understood",[66,39819,39820],{},"release window is appropriate for the risk",[52,39822,39824],{"id":39823},"post-migration-verification","Post-migration verification",[16,39826,39827,39828,39830],{},"After ",[20,39829,10296],{},", confirm:",[106,39832,39834],{"className":108,"code":39833,"language":110,"meta":111,"style":111},"curl -f https:\u002F\u002Fexample.com\u002Fhealthz\npython manage.py showmigrations\nsudo journalctl -u gunicorn -n 100 --no-pager\n",[20,39835,39836,39844,39852],{"__ignoreMap":111},[115,39837,39838,39840,39842],{"class":117,"line":118},[115,39839,2764],{"class":262},[115,39841,2777],{"class":202},[115,39843,2780],{"class":132},[115,39845,39846,39848,39850],{"class":117,"line":136},[115,39847,1114],{"class":262},[115,39849,1117],{"class":132},[115,39851,1129],{"class":132},[115,39853,39854,39856,39858,39860,39862,39864,39866],{"class":117,"line":149},[115,39855,2001],{"class":262},[115,39857,5030],{"class":132},[115,39859,2788],{"class":202},[115,39861,2791],{"class":132},[115,39863,2794],{"class":202},[115,39865,2797],{"class":202},[115,39867,2800],{"class":202},[16,39869,31301],{},[63,39871,39872,39875,39878,39881],{},[66,39873,39874],{},"app starts without import or model errors",[66,39876,39877],{},"migration is marked applied",[66,39879,39880],{},"critical pages and login or admin flows work",[66,39882,39883],{},"no spike in 500s or database connection errors",[52,39885,39887],{"id":39886},"database-level-verification","Database-level verification",[16,39889,39890],{},"For risky changes, check the database directly.",[16,39892,38745],{},[63,39894,39895,39898,39901,39904],{},[66,39896,39897],{},"confirm new columns or tables exist",[66,39899,39900],{},"confirm row counts match expectations after a backfill",[66,39902,39903],{},"confirm new indexes exist",[66,39905,39906],{},"confirm query performance is still acceptable on affected paths",[16,39908,39909],{},"For PostgreSQL, a direct verification step may look like:",[106,39911,39913],{"className":11064,"code":39912,"language":11066,"meta":111,"style":111},"\\d+ app_model\nSELECT COUNT(*) FROM app_model WHERE new_field IS NULL;\n",[20,39914,39915,39926],{"__ignoreMap":111},[115,39916,39917,39920,39923],{"class":117,"line":118},[115,39918,39919],{"class":125},"\\d",[115,39921,39922],{"class":121},"+",[115,39924,39925],{"class":125}," app_model\n",[115,39927,39928,39930,39933,39935,39938,39940,39942,39945,39947,39950,39953,39956],{"class":117,"line":136},[115,39929,11073],{"class":121},[115,39931,39932],{"class":202}," COUNT",[115,39934,37145],{"class":125},[115,39936,39937],{"class":121},"*",[115,39939,18281],{"class":125},[115,39941,11089],{"class":121},[115,39943,39944],{"class":125}," app_model ",[115,39946,11097],{"class":121},[115,39948,39949],{"class":125}," new_field ",[115,39951,39952],{"class":121},"IS",[115,39954,39955],{"class":121}," NULL",[115,39957,3811],{"class":125},[16,39959,39960],{},"Application health alone does not prove the data state is correct.",[11,39962,39964],{"id":39963},"step-5-plan-rollback-and-recovery-before-running-migrate","Step 5: Plan rollback and recovery before running migrate",[16,39966,39967],{},"A safe Django rollback strategy for migrations starts with a realistic assumption: app rollback is easier than schema rollback.",[52,39969,39971],{"id":39970},"recovery-rules","Recovery rules",[63,39973,39974,39977,39980,39983],{},[66,39975,39976],{},"If migration fails before completion: stop the release, inspect the error, and do not restart the app into a partially compatible state.",[66,39978,39979],{},"If migration succeeds but the app fails: roll back app code first if the schema remains backward-compatible.",[66,39981,39982],{},"If a data migration partially completes: stop further rollout, assess data consistency, and continue with a targeted recovery plan.",[66,39984,39985],{},"If migration causes availability issues: pause deploy, consider reverting traffic to the old app version only if schema compatibility allows it.",[16,39987,39988],{},"A targeted recovery command may look like this, but only after verifying the migration path is reversible and safe for production data:",[106,39990,39992],{"className":108,"code":39991,"language":110,"meta":111,"style":111},"python manage.py migrate app_name 000X\n",[20,39993,39994],{"__ignoreMap":111},[115,39995,39996,39998,40000,40002,40004],{"class":117,"line":118},[115,39997,1114],{"class":262},[115,39999,1117],{"class":132},[115,40001,1826],{"class":132},[115,40003,38284],{"class":132},[115,40005,39395],{"class":132},[16,40007,40008],{},"Even if Django can technically reverse a migration, that does not mean reversal is safe on production data. Reverse migrations are often the wrong recovery path for destructive, locking, or partially applied data changes.",[52,40010,40012],{"id":40011},"practical-rollback-policy","Practical rollback policy",[16,40014,40015],{},"For most teams:",[63,40017,40018,40021,40024],{},[66,40019,40020],{},"prefer backward-compatible schema design so app code can be reverted safely",[66,40022,40023],{},"treat backup restore as a serious recovery action, not a routine rollback method",[66,40025,40026],{},"document irreversible migrations explicitly in the release notes",[16,40028,40029],{},"If the migration is marked irreversible or includes destructive data changes, the rollback path may be “restore database from backup and redeploy known-good code.” That is slow, so use it only when necessary.",[11,40031,1321],{"id":1320},[16,40033,40034],{},"This strategy works because it separates four concerns that are often mixed together:",[63,40036,40037,40040,40043,40046],{},[66,40038,40039],{},"schema change",[66,40041,40042],{},"application rollout",[66,40044,40045],{},"data backfill",[66,40047,40048],{},"recovery planning",[16,40050,40051],{},"Most production migration incidents happen when those are combined into one opaque step. The safe approach is to make schema changes compatible first, run migrations once, and deploy app code only after the database is in the expected state.",[16,40053,40054],{},"This is also why Django migration best practices in production focus on sequencing rather than just command usage. A technically correct migration file can still be unsafe if it requires exact timing between code and schema or if every replica tries to run it at startup.",[52,40056,40058],{"id":40057},"when-to-convert-this-into-a-reusable-script-or-template","When to convert this into a reusable script or template",[16,40060,40061],{},"Once your team repeats the same release flow, script the parts that are mechanical: migration checks, backup verification, single-runner migration jobs, health checks, and deploy locks. A reusable template is especially helpful when you have multiple Django services or multiple environments with the same release order. Keep migration review manual for high-risk changes.",[11,40063,11443],{"id":11442},[52,40065,40067],{"id":40066},"zero-downtime-expectations","Zero-downtime expectations",[16,40069,40070],{},"True zero-downtime Django migrations depend on database behavior, schema compatibility, and rollout design. Django alone does not make destructive or locking changes safe.",[52,40072,40074],{"id":40073},"large-tables","Large tables",[16,40076,40077],{},"For large tables and high traffic:",[63,40079,40080,40083,40086],{},[66,40081,40082],{},"avoid one-shot backfills during the main deploy",[66,40084,40085],{},"test lock behavior in staging with realistic data volume",[66,40087,40088],{},"prefer batched updates and delayed constraint enforcement",[52,40090,40092],{"id":40091},"multi-server-or-blue-green-deployments","Multi-server or blue-green deployments",[16,40094,40095],{},"If old and new versions may run at the same time, your schema must support both. This is where incompatible renames, drops, and early constraint enforcement break otherwise healthy rolling deploys.",[52,40097,40099],{"id":40098},"access-control","Access control",[16,40101,40102],{},"Limit who can run production migrations. In CI\u002FCD, use a dedicated release job with audited credentials. In manual deploys, avoid broad shell access when only migration execution is needed.",[11,40104,1386],{"id":1385},[16,40106,40107,40108,211],{},"For the broader release flow, see the ",[1395,40109,32365],{"href":2999},[16,40111,40112,40113,211],{},"If you need the application server and reverse proxy layer, use ",[1395,40114,32215],{"href":2985},[16,40116,40117,40118,211],{},"For pipeline design, see the ",[1395,40119,40121],{"href":40120},"\u002Fdeploy\u002Fdeploy-django-with-github-actions-cicd","Django CI\u002FCD pipeline for safe deployments",[16,40123,40124,40125,211],{},"If a release goes wrong, follow ",[1395,40126,40128],{"href":40127},"\u002Ffix-issues\u002Ffix-django-migration-failed-during-deploy","how to fix failed Django migrations in production",[11,40130,1420],{"id":1419},[52,40132,40134],{"id":40133},"should-django-migrations-run-before-or-after-restarting-the-app","Should Django migrations run before or after restarting the app?",[16,40136,40137],{},"For backward-compatible migrations, run them in a controlled step before restarting or rolling the app. For incompatible changes, split the work across multiple releases so old and new code are never forced against a schema they cannot handle.",[52,40139,40141,40142,40145],{"id":40140},"can-i-run-managepy-migrate-on-every-app-container-startup","Can I run ",[20,40143,40144],{},"manage.py migrate"," on every app container startup?",[16,40147,40148],{},"No. That is unsafe in multi-replica deployments because multiple containers may race to apply the same migration. Use one dedicated migration job or one release task.",[52,40150,40152],{"id":40151},"what-is-the-safest-way-to-deploy-a-django-migration-that-changes-a-large-table","What is the safest way to deploy a Django migration that changes a large table?",[16,40154,40155],{},"Use an expand-and-contract approach. Ship the schema change first, keep app compatibility, perform batched backfills separately, and enforce constraints or remove old fields in a later release.",[52,40157,40159],{"id":40158},"can-django-migrations-be-rolled-back-safely-in-production","Can Django migrations be rolled back safely in production?",[16,40161,40162],{},"Sometimes, but not always. App rollback is usually easier than database rollback. Some migrations are irreversible, and others are technically reversible but still unsafe to reverse on live data.",[52,40164,40166],{"id":40165},"what-is-the-safest-rollback-option-if-a-migration-succeeds-but-the-release-fails","What is the safest rollback option if a migration succeeds but the release fails?",[16,40168,40169],{},"If the schema is backward-compatible, roll back the app code first and keep the migrated schema in place. Restore the database from backup only for serious failures that cannot be resolved safely with code rollback or a verified recovery plan.",[1485,40171,40172],{},"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 .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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":111,"searchDepth":149,"depth":149,"links":40174},[40175,40176,40177,40178,40183,40187,40193,40198,40202,40205,40211,40212],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":39274,"depth":136,"text":39275,"children":40179},[40180,40181,40182],{"id":39281,"depth":149,"text":39282},{"id":39302,"depth":149,"text":39303},{"id":39323,"depth":149,"text":39324},{"id":39411,"depth":136,"text":39412,"children":40184},[40185,40186],{"id":39418,"depth":149,"text":39419},{"id":39481,"depth":149,"text":39482},{"id":39504,"depth":136,"text":39505,"children":40188},[40189,40190,40191,40192],{"id":39511,"depth":149,"text":39512},{"id":39657,"depth":149,"text":39658},{"id":39684,"depth":149,"text":39685},{"id":39728,"depth":149,"text":39729},{"id":39759,"depth":136,"text":39760,"children":40194},[40195,40196,40197],{"id":39763,"depth":149,"text":39764},{"id":39823,"depth":149,"text":39824},{"id":39886,"depth":149,"text":39887},{"id":39963,"depth":136,"text":39964,"children":40199},[40200,40201],{"id":39970,"depth":149,"text":39971},{"id":40011,"depth":149,"text":40012},{"id":1320,"depth":136,"text":1321,"children":40203},[40204],{"id":40057,"depth":149,"text":40058},{"id":11442,"depth":136,"text":11443,"children":40206},[40207,40208,40209,40210],{"id":40066,"depth":149,"text":40067},{"id":40073,"depth":149,"text":40074},{"id":40091,"depth":149,"text":40092},{"id":40098,"depth":149,"text":40099},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":40213},[40214,40215,40217,40218,40219],{"id":40133,"depth":149,"text":40134},{"id":40140,"depth":149,"text":40216},"Can I run manage.py migrate on every app container startup?",{"id":40151,"depth":149,"text":40152},{"id":40158,"depth":149,"text":40159},{"id":40165,"depth":149,"text":40166},{},"\u002Fdjango-migrations-deployment-strategy",[1397,40223,35630],"\u002Foptimize\u002Fdjango-deployment-security-basics",{"title":39199,"description":39206},[1557,1558],"django-migrations-deployment-strategy",[1557,1558],"b9muYMmR7tezzeDatc9wCHpODHdcXXBjJijJKBDbuf8",{"id":40230,"title":3007,"body":40231,"category":34157,"description":41997,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":41998,"navigation":309,"path":41999,"priority":35628,"related":42000,"role":34162,"section":34163,"seo":42001,"stack":42002,"stem":42003,"tags":42004,"type":3104,"__hash__":42005},"articles\u002Fdjango-production-settings-checklist.md",{"type":8,"value":40232,"toc":41930},[40233,40235,40241,40244,40265,40271,40292,40297,40299,40302,40335,40338,40340,40344,40347,40350,40370,40373,40489,40492,40586,40589,40592,40595,40609,40611,40617,40619,40625,40628,40645,40648,40662,40666,40671,40675,40678,40689,40692,40695,40713,40716,40733,40736,40739,40741,40747,40750,40763,40766,40791,40794,40799,40802,40808,40822,40825,40843,40845,40861,40868,40879,40881,40888,40896,40899,40927,40930,40983,40986,40994,40998,41001,41017,41020,41023,41041,41044,41047,41051,41054,41094,41100,41103,41106,41119,41126,41146,41149,41153,41163,41165,41169,41174,41177,41205,41208,41259,41261,41269,41273,41276,41287,41291,41294,41305,41310,41314,41332,41336,41339,41353,41356,41374,41376,41392,41394,41398,41401,41406,41409,41429,41436,41441,41446,41459,41462,41468,41471,41499,41502,41505,41517,41520,41522,41526,41530,41564,41568,41571,41585,41589,41603,41606,41632,41635,41658,41662,41687,41690,41706,41710,41713,41730,41732,41735,41760,41763,41767,41774,41776,41819,41821,41827,41835,41841,41850,41852,41859,41862,41869,41877,41881,41884,41907,41914,41917,41924,41927],[11,40234,14],{"id":13},[16,40236,40237,40238,40240],{},"A lot of Django production incidents come from ",[20,40239,10342],{},", not application code.",[16,40242,40243],{},"The common pattern is simple: a project works locally, gets deployed, and production still carries development defaults or half-finished security settings. That usually shows up in one of two ways:",[63,40245,40246,40256],{},[66,40247,40248,40251,40252,40255],{},[1226,40249,40250],{},"unsafe exposure",", such as ",[20,40253,40254],{},"DEBUG = True"," leaking detailed error pages",[66,40257,40258,40261,40262,40264],{},[1226,40259,40260],{},"broken traffic",", such as bad ",[20,40263,2719],{}," or CSRF settings causing 400 or 403 errors after deployment",[16,40266,40267,40268,40270],{},"This page is a practical ",[1226,40269,35524],{}," focused on the minimum settings you should verify before and after a release:",[63,40272,40273,40277,40281,40285,40289],{},[66,40274,40275],{},[20,40276,2713],{},[66,40278,40279],{},[20,40280,7350],{},[66,40282,40283],{},[20,40284,2719],{},[66,40286,40287],{},[20,40288,2725],{},[66,40290,40291],{},"proxy and HTTPS settings that affect host and CSRF validation",[16,40293,34291,40294,40296],{},[1226,40295,7474],{}," try to cover full database tuning, complete secrets management, or every security header Django supports.",[11,40298,30],{"id":29},[16,40300,40301],{},"Before your first production deploy:",[63,40303,40304,40310,40314,40319,40325,40328,40332],{},[66,40305,40306,40307,40309],{},"load a unique production ",[20,40308,2713],{}," from the environment or secret manager",[66,40311,3192,40312],{},[20,40313,32431],{},[66,40315,40316,40317],{},"define explicit ",[20,40318,2719],{},[66,40320,40321,40322,40324],{},"configure ",[20,40323,2725],{}," only when your deployment actually needs it",[66,40326,40327],{},"make sure Django correctly detects HTTPS when you are behind Nginx, Caddy, or a load balancer",[66,40329,7902,40330],{},[20,40331,15970],{},[66,40333,40334],{},"verify with real requests, a form POST, and log inspection after restart",[16,40336,40337],{},"If any of these changes break traffic, roll back to the previous environment file or previous release artifact, restart the app, confirm the old service is healthy, and re-run smoke tests.",[11,40339,43],{"id":42},[11,40341,40343],{"id":40342},"_1-start-with-separate-production-settings","1. Start with separate production settings",[16,40345,40346],{},"Do not let production inherit local defaults by accident.",[16,40348,40349],{},"Two common layouts are fine:",[63,40351,40352,40358],{},[66,40353,40354,40355,40357],{},"one ",[20,40356,10342],{}," with environment-variable driven values",[66,40359,40360,40361,1153,40364,20346,40367],{},"split modules such as ",[20,40362,40363],{},"base.py",[20,40365,40366],{},"development.py",[20,40368,40369],{},"production.py",[16,40371,40372],{},"A simple split example:",[106,40374,40376],{"className":2369,"code":40375,"language":1114,"meta":111,"style":111},"# mysite\u002Fsettings\u002Fproduction.py\nfrom .base import *\nimport os\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\nDEBUG = False\nALLOWED_HOSTS = [h.strip() for h in os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"\").split(\",\") if h.strip()]\nCSRF_TRUSTED_ORIGINS = [o.strip() for o in os.environ.get(\"DJANGO_CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if o.strip()]\n",[20,40377,40378,40383,40395,40401,40405,40417,40425,40457],{"__ignoreMap":111},[115,40379,40380],{"class":117,"line":118},[115,40381,40382],{"class":3861},"# mysite\u002Fsettings\u002Fproduction.py\n",[115,40384,40385,40387,40390,40392],{"class":117,"line":136},[115,40386,5621],{"class":121},[115,40388,40389],{"class":125}," .base ",[115,40391,5613],{"class":121},[115,40393,40394],{"class":121}," *\n",[115,40396,40397,40399],{"class":117,"line":149},[115,40398,5613],{"class":121},[115,40400,5616],{"class":125},[115,40402,40403],{"class":117,"line":162},[115,40404,310],{"emptyLinePlaceholder":309},[115,40406,40407,40409,40411,40413,40415],{"class":117,"line":175},[115,40408,2713],{"class":202},[115,40410,2380],{"class":121},[115,40412,8861],{"class":125},[115,40414,12063],{"class":132},[115,40416,2552],{"class":125},[115,40418,40419,40421,40423],{"class":117,"line":350},[115,40420,7350],{"class":202},[115,40422,2380],{"class":121},[115,40424,7355],{"class":202},[115,40426,40427,40429,40431,40433,40435,40437,40439,40441,40443,40445,40447,40449,40451,40453,40455],{"class":117,"line":365},[115,40428,2719],{"class":202},[115,40430,2380],{"class":121},[115,40432,18253],{"class":125},[115,40434,18256],{"class":121},[115,40436,18259],{"class":125},[115,40438,18262],{"class":121},[115,40440,8884],{"class":125},[115,40442,25202],{"class":132},[115,40444,1153],{"class":125},[115,40446,18272],{"class":132},[115,40448,18275],{"class":125},[115,40450,18278],{"class":132},[115,40452,18281],{"class":125},[115,40454,10833],{"class":121},[115,40456,18286],{"class":125},[115,40458,40459,40461,40463,40465,40467,40469,40471,40473,40475,40477,40479,40481,40483,40485,40487],{"class":117,"line":380},[115,40460,2725],{"class":202},[115,40462,2380],{"class":121},[115,40464,18295],{"class":125},[115,40466,18256],{"class":121},[115,40468,18300],{"class":125},[115,40470,18262],{"class":121},[115,40472,8884],{"class":125},[115,40474,25237],{"class":132},[115,40476,1153],{"class":125},[115,40478,18272],{"class":132},[115,40480,18275],{"class":125},[115,40482,18278],{"class":132},[115,40484,18281],{"class":125},[115,40486,10833],{"class":121},[115,40488,18322],{"class":125},[16,40490,40491],{},"A safer boolean parser for environment variables:",[106,40493,40495],{"className":2369,"code":40494,"language":1114,"meta":111,"style":111},"import os\n\ndef env_bool(name, default=False):\n    return os.environ.get(name, str(default)).lower() in {\"1\", \"true\", \"yes\", \"on\"}\n\nDEBUG = env_bool(\"DJANGO_DEBUG\", default=False)\n",[20,40496,40497,40503,40507,40523,40560,40564],{"__ignoreMap":111},[115,40498,40499,40501],{"class":117,"line":118},[115,40500,5613],{"class":121},[115,40502,5616],{"class":125},[115,40504,40505],{"class":117,"line":136},[115,40506,310],{"emptyLinePlaceholder":309},[115,40508,40509,40511,40514,40517,40519,40521],{"class":117,"line":149},[115,40510,8808],{"class":121},[115,40512,40513],{"class":262}," env_bool",[115,40515,40516],{"class":125},"(name, default",[115,40518,129],{"class":121},[115,40520,3364],{"class":202},[115,40522,37156],{"class":125},[115,40524,40525,40527,40530,40533,40536,40538,40540,40543,40545,40548,40550,40553,40555,40558],{"class":117,"line":162},[115,40526,3822],{"class":121},[115,40528,40529],{"class":125}," os.environ.get(name, ",[115,40531,40532],{"class":202},"str",[115,40534,40535],{"class":125},"(default)).lower() ",[115,40537,18262],{"class":121},[115,40539,37166],{"class":125},[115,40541,40542],{"class":132},"\"1\"",[115,40544,1153],{"class":125},[115,40546,40547],{"class":132},"\"true\"",[115,40549,1153],{"class":125},[115,40551,40552],{"class":132},"\"yes\"",[115,40554,1153],{"class":125},[115,40556,40557],{"class":132},"\"on\"",[115,40559,2323],{"class":125},[115,40561,40562],{"class":117,"line":175},[115,40563,310],{"emptyLinePlaceholder":309},[115,40565,40566,40568,40570,40573,40575,40577,40580,40582,40584],{"class":117,"line":350},[115,40567,7350],{"class":202},[115,40569,2380],{"class":121},[115,40571,40572],{"class":125}," env_bool(",[115,40574,25164],{"class":132},[115,40576,1153],{"class":125},[115,40578,40579],{"class":5680},"default",[115,40581,129],{"class":121},[115,40583,3364],{"class":202},[115,40585,2394],{"class":125},[16,40587,40588],{},"If you use a single settings file, still default to safe production behavior unless you explicitly enable development mode.",[52,40590,14174],{"id":40591},"verify",[16,40593,40594],{},"Run a deploy check before restart:",[106,40596,40597],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,40598,40599],{"__ignoreMap":111},[115,40600,40601,40603,40605,40607],{"class":117,"line":118},[115,40602,1114],{"class":262},[115,40604,1117],{"class":132},[115,40606,1814],{"class":132},[115,40608,1817],{"class":202},[52,40610,4956],{"id":4955},[16,40612,40613,40614,40616],{},"Keep the previous ",[20,40615,191],{}," file or previous release config available. If host or CSRF validation breaks production, restore the last known-good values, restart the application server, and confirm the previous process came back cleanly in logs and health checks.",[23099,40618],{},[11,40620,40622,40623],{"id":40621},"_2-load-a-real-production-secret_key","2. Load a real production ",[20,40624,2713],{},[16,40626,40627],{},"A production deployment should never use a development, test, or placeholder secret key.",[106,40629,40631],{"className":2369,"code":40630,"language":1114,"meta":111,"style":111},"SECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\n",[20,40632,40633],{"__ignoreMap":111},[115,40634,40635,40637,40639,40641,40643],{"class":117,"line":118},[115,40636,2713],{"class":202},[115,40638,2380],{"class":121},[115,40640,8861],{"class":125},[115,40642,12063],{"class":132},[115,40644,2552],{"class":125},[16,40646,40647],{},"Example environment value generation:",[106,40649,40651],{"className":108,"code":40650,"language":110,"meta":111,"style":111},"python -c \"from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())\"\n",[20,40652,40653],{"__ignoreMap":111},[115,40654,40655,40657,40659],{"class":117,"line":118},[115,40656,1114],{"class":262},[115,40658,1024],{"class":202},[115,40660,40661],{"class":132}," \"from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())\"\n",[52,40663,40665],{"id":40664},"why-it-matters","Why it matters",[16,40667,40668,40670],{},[20,40669,2713],{}," is used for Django cryptographic signing, including sessions, password reset tokens, and other security-sensitive features. A reused or committed key weakens the whole deployment.",[52,40672,40674],{"id":40673},"what-to-avoid","What to avoid",[16,40676,40677],{},"Do not:",[63,40679,40680,40683,40686],{},[66,40681,40682],{},"hardcode a shared dev key into production settings",[66,40684,40685],{},"commit the production key to Git",[66,40687,40688],{},"reuse the same key across unrelated projects",[52,40690,14174],{"id":40691},"verify-1",[16,40693,40694],{},"Confirm the environment variable is present in the runtime environment your app actually uses, then restart and check logs:",[106,40696,40697],{"className":108,"code":23806,"language":110,"meta":111,"style":111},[20,40698,40699],{"__ignoreMap":111},[115,40700,40701,40703,40705,40707,40709,40711],{"class":117,"line":118},[115,40702,2785],{"class":262},[115,40704,2788],{"class":202},[115,40706,2791],{"class":132},[115,40708,2794],{"class":202},[115,40710,15523],{"class":202},[115,40712,2800],{"class":202},[16,40714,40715],{},"Or in Docker:",[106,40717,40719],{"className":108,"code":40718,"language":110,"meta":111,"style":111},"docker compose logs web --tail=50\n",[20,40720,40721],{"__ignoreMap":111},[115,40722,40723,40725,40727,40729,40731],{"class":117,"line":118},[115,40724,3295],{"class":262},[115,40726,3298],{"class":132},[115,40728,3301],{"class":132},[115,40730,3304],{"class":132},[115,40732,26404],{"class":202},[52,40734,4956],{"id":40735},"rollback-note-1",[16,40737,40738],{},"If a bad key value was deployed and the app fails to start, restore the previous environment file or secret reference, restart the app, and verify the old process is serving traffic again.",[23099,40740],{},[11,40742,40744,40745],{"id":40743},"_3-set-debug-false","3. Set ",[20,40746,32431],{},[16,40748,40749],{},"This is the first item in any Django production checklist.",[106,40751,40753],{"className":2369,"code":40752,"language":1114,"meta":111,"style":111},"DEBUG = False\n",[20,40754,40755],{"__ignoreMap":111},[115,40756,40757,40759,40761],{"class":117,"line":118},[115,40758,7350],{"class":202},[115,40760,2380],{"class":121},[115,40762,7355],{"class":202},[16,40764,40765],{},"If you load it from the environment:",[106,40767,40769],{"className":2369,"code":40768,"language":1114,"meta":111,"style":111},"DEBUG = env_bool(\"DJANGO_DEBUG\", default=False)\n",[20,40770,40771],{"__ignoreMap":111},[115,40772,40773,40775,40777,40779,40781,40783,40785,40787,40789],{"class":117,"line":118},[115,40774,7350],{"class":202},[115,40776,2380],{"class":121},[115,40778,40572],{"class":125},[115,40780,25164],{"class":132},[115,40782,1153],{"class":125},[115,40784,40579],{"class":5680},[115,40786,129],{"class":121},[115,40788,3364],{"class":202},[115,40790,2394],{"class":125},[52,40792,40665],{"id":40793},"why-it-matters-1",[16,40795,36576,40796,40798],{},[20,40797,40254],{},", Django may expose stack traces, settings context, paths, and other internal details through browser error pages. It also changes assumptions around static files and error handling.",[52,40800,14174],{"id":40801},"verify-2",[16,40803,40804,40805,40807],{},"Trigger a harmless missing page and confirm you do ",[1226,40806,7474],{}," see a Django traceback:",[106,40809,40811],{"className":108,"code":40810,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fthis-page-does-not-exist\n",[20,40812,40813],{"__ignoreMap":111},[115,40814,40815,40817,40819],{"class":117,"line":118},[115,40816,2764],{"class":262},[115,40818,2767],{"class":202},[115,40820,40821],{"class":132}," https:\u002F\u002Fexample.com\u002Fthis-page-does-not-exist\n",[16,40823,40824],{},"For server-side confirmation, inspect logs instead of browser output:",[106,40826,40827],{"className":108,"code":3266,"language":110,"meta":111,"style":111},[20,40828,40829],{"__ignoreMap":111},[115,40830,40831,40833,40835,40837,40839,40841],{"class":117,"line":118},[115,40832,2785],{"class":262},[115,40834,2788],{"class":202},[115,40836,2791],{"class":132},[115,40838,2794],{"class":202},[115,40840,2797],{"class":202},[115,40842,2800],{"class":202},[16,40844,40715],{},[106,40846,40847],{"className":108,"code":3288,"language":110,"meta":111,"style":111},[20,40848,40849],{"__ignoreMap":111},[115,40850,40851,40853,40855,40857,40859],{"class":117,"line":118},[115,40852,3295],{"class":262},[115,40854,3298],{"class":132},[115,40856,3301],{"class":132},[115,40858,3304],{"class":132},[115,40860,3307],{"class":202},[52,40862,40864,40865,40867],{"id":40863},"failure-patterns-when-debug-is-left-on","Failure patterns when ",[20,40866,7350],{}," is left on",[63,40869,40870,40873,40876],{},[66,40871,40872],{},"detailed exception pages visible to users",[66,40874,40875],{},"leaked configuration details",[66,40877,40878],{},"confusion around static file behavior that only worked in development",[23099,40880],{},[11,40882,40884,40885,40887],{"id":40883},"_4-set-allowed_hosts-explicitly","4. Set ",[20,40886,2719],{}," explicitly",[16,40889,40890,40892,40893,40895],{},[20,40891,2719],{}," protects Django against invalid or unexpected ",[20,40894,3648],{}," headers.",[16,40897,40898],{},"A typical production example:",[106,40900,40901],{"className":2369,"code":3529,"language":1114,"meta":111,"style":111},[20,40902,40903,40911,40917,40923],{"__ignoreMap":111},[115,40904,40905,40907,40909],{"class":117,"line":118},[115,40906,2719],{"class":202},[115,40908,2380],{"class":121},[115,40910,3540],{"class":125},[115,40912,40913,40915],{"class":117,"line":136},[115,40914,3545],{"class":132},[115,40916,3354],{"class":125},[115,40918,40919,40921],{"class":117,"line":149},[115,40920,3552],{"class":132},[115,40922,3354],{"class":125},[115,40924,40925],{"class":117,"line":162},[115,40926,2552],{"class":125},[16,40928,40929],{},"From an environment variable:",[106,40931,40933],{"className":2369,"code":40932,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\n    h.strip()\n    for h in os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"\").split(\",\")\n    if h.strip()\n]\n",[20,40934,40935,40943,40948,40971,40979],{"__ignoreMap":111},[115,40936,40937,40939,40941],{"class":117,"line":118},[115,40938,2719],{"class":202},[115,40940,2380],{"class":121},[115,40942,3540],{"class":125},[115,40944,40945],{"class":117,"line":136},[115,40946,40947],{"class":125},"    h.strip()\n",[115,40949,40950,40953,40955,40957,40959,40961,40963,40965,40967,40969],{"class":117,"line":149},[115,40951,40952],{"class":121},"    for",[115,40954,18259],{"class":125},[115,40956,18262],{"class":121},[115,40958,8884],{"class":125},[115,40960,25202],{"class":132},[115,40962,1153],{"class":125},[115,40964,18272],{"class":132},[115,40966,18275],{"class":125},[115,40968,18278],{"class":132},[115,40970,2394],{"class":125},[115,40972,40973,40976],{"class":117,"line":162},[115,40974,40975],{"class":121},"    if",[115,40977,40978],{"class":125}," h.strip()\n",[115,40980,40981],{"class":117,"line":175},[115,40982,2552],{"class":125},[16,40984,40985],{},"Example environment value:",[106,40987,40988],{"className":2329,"code":15020,"language":2331,"meta":111,"style":111},[20,40989,40990],{"__ignoreMap":111},[115,40991,40992],{"class":117,"line":118},[115,40993,15020],{},[52,40995,40997],{"id":40996},"what-to-include","What to include",[16,40999,41000],{},"Include only hosts that should actually serve the app:",[63,41002,41003,41006,41011,41014],{},[66,41004,41005],{},"primary domain",[66,41007,41008,41010],{},[20,41009,4246],{}," domain if used",[66,41012,41013],{},"internal hostname only if your architecture really sends that host to Django",[66,41015,41016],{},"IP address only if you intentionally support direct IP access",[52,41018,40674],{"id":41019},"what-to-avoid-1",[16,41021,41022],{},"Do not use:",[106,41024,41026],{"className":2369,"code":41025,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\"*\"]\n",[20,41027,41028],{"__ignoreMap":111},[115,41029,41030,41032,41034,41036,41039],{"class":117,"line":118},[115,41031,2719],{"class":202},[115,41033,2380],{"class":121},[115,41035,7493],{"class":125},[115,41037,41038],{"class":132},"\"*\"",[115,41040,2552],{"class":125},[16,41042,41043],{},"That removes an important validation layer and can hide routing or proxy mistakes.",[16,41045,41046],{},"Also remove stale domains from old staging or preview environments.",[52,41048,41050],{"id":41049},"reverse-proxy-note","Reverse proxy note",[16,41052,41053],{},"Your reverse proxy should pass the original host header through to Django. For Nginx:",[106,41055,41056],{"className":2154,"code":31475,"language":2156,"meta":111,"style":111},[20,41057,41058,41066,41072,41078,41084,41090],{"__ignoreMap":111},[115,41059,41060,41062,41064],{"class":117,"line":118},[115,41061,7128],{"class":121},[115,41063,2268],{"class":262},[115,41065,2220],{"class":125},[115,41067,41068,41070],{"class":117,"line":136},[115,41069,7137],{"class":121},[115,41071,3748],{"class":125},[115,41073,41074,41076],{"class":117,"line":149},[115,41075,7144],{"class":121},[115,41077,2288],{"class":125},[115,41079,41080,41082],{"class":117,"line":162},[115,41081,7144],{"class":121},[115,41083,2312],{"class":125},[115,41085,41086,41088],{"class":117,"line":175},[115,41087,7144],{"class":121},[115,41089,2304],{"class":125},[115,41091,41092],{"class":117,"line":350},[115,41093,2323],{"class":125},[16,41095,41096,41097,41099],{},"If the proxy rewrites ",[20,41098,3648],{}," incorrectly, Django host validation becomes misleading.",[52,41101,14174],{"id":41102},"verify-3",[16,41104,41105],{},"Check the expected host:",[106,41107,41109],{"className":108,"code":41108,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002F\n",[20,41110,41111],{"__ignoreMap":111},[115,41112,41113,41115,41117],{"class":117,"line":118},[115,41114,2764],{"class":262},[115,41116,2767],{"class":202},[115,41118,32984],{"class":132},[16,41120,41121,41122,41125],{},"Then check an unexpected host using a method that still reaches Django, such as plain HTTP on the server IP inside the trusted network or ",[20,41123,41124],{},"curl --resolve"," for HTTPS:",[106,41127,41129],{"className":108,"code":41128,"language":110,"meta":111,"style":111},"curl -I --resolve bad.example.com:443:server-ip https:\u002F\u002Fbad.example.com\u002F\n",[20,41130,41131],{"__ignoreMap":111},[115,41132,41133,41135,41137,41140,41143],{"class":117,"line":118},[115,41134,2764],{"class":262},[115,41136,2767],{"class":202},[115,41138,41139],{"class":202}," --resolve",[115,41141,41142],{"class":132}," bad.example.com:443:server-ip",[115,41144,41145],{"class":132}," https:\u002F\u002Fbad.example.com\u002F\n",[16,41147,41148],{},"The bad host request should fail cleanly rather than being served as your real site.",[52,41150,41152],{"id":41151},"rollback","Rollback",[16,41154,41155,41156,41159,41160,41162],{},"If valid traffic suddenly starts returning ",[20,41157,41158],{},"400 Bad Request",", restore the previous host list, restart the app, verify the proxy is forwarding the correct ",[20,41161,3648],{}," header, and confirm the old process is healthy before ending the rollback.",[23099,41164],{},[11,41166,41168],{"id":41167},"_5-configure-csrf-safely-for-production","5. Configure CSRF safely for production",[16,41170,41171,41173],{},[20,41172,2725],{}," is not always required, but when it is required, it must be exact.",[16,41175,41176],{},"Typical example:",[106,41178,41179],{"className":2369,"code":3567,"language":1114,"meta":111,"style":111},[20,41180,41181,41189,41195,41201],{"__ignoreMap":111},[115,41182,41183,41185,41187],{"class":117,"line":118},[115,41184,2725],{"class":202},[115,41186,2380],{"class":121},[115,41188,3540],{"class":125},[115,41190,41191,41193],{"class":117,"line":136},[115,41192,3582],{"class":132},[115,41194,3354],{"class":125},[115,41196,41197,41199],{"class":117,"line":149},[115,41198,3589],{"class":132},[115,41200,3354],{"class":125},[115,41202,41203],{"class":117,"line":162},[115,41204,2552],{"class":125},[16,41206,41207],{},"Environment-driven version:",[106,41209,41211],{"className":2369,"code":41210,"language":1114,"meta":111,"style":111},"CSRF_TRUSTED_ORIGINS = [\n    o.strip()\n    for o in os.environ.get(\"DJANGO_CSRF_TRUSTED_ORIGINS\", \"\").split(\",\")\n    if o.strip()\n]\n",[20,41212,41213,41221,41226,41248,41255],{"__ignoreMap":111},[115,41214,41215,41217,41219],{"class":117,"line":118},[115,41216,2725],{"class":202},[115,41218,2380],{"class":121},[115,41220,3540],{"class":125},[115,41222,41223],{"class":117,"line":136},[115,41224,41225],{"class":125},"    o.strip()\n",[115,41227,41228,41230,41232,41234,41236,41238,41240,41242,41244,41246],{"class":117,"line":149},[115,41229,40952],{"class":121},[115,41231,18300],{"class":125},[115,41233,18262],{"class":121},[115,41235,8884],{"class":125},[115,41237,25237],{"class":132},[115,41239,1153],{"class":125},[115,41241,18272],{"class":132},[115,41243,18275],{"class":125},[115,41245,18278],{"class":132},[115,41247,2394],{"class":125},[115,41249,41250,41252],{"class":117,"line":162},[115,41251,40975],{"class":121},[115,41253,41254],{"class":125}," o.strip()\n",[115,41256,41257],{"class":117,"line":175},[115,41258,2552],{"class":125},[16,41260,40985],{},[106,41262,41263],{"className":2329,"code":15025,"language":2331,"meta":111,"style":111},[20,41264,41265],{"__ignoreMap":111},[115,41266,41267],{"class":117,"line":118},[115,41268,15025],{},[52,41270,41272],{"id":41271},"when-it-is-needed","When it is needed",[16,41274,41275],{},"You commonly need this when:",[63,41277,41278,41281,41284],{},[66,41279,41280],{},"admin or forms are accessed through a different trusted origin",[66,41282,41283],{},"you intentionally allow cross-origin browser POSTs",[66,41285,41286],{},"Django is rejecting secure form submissions because the request origin seen by Django does not match the trusted origin, often due to proxy\u002Fscheme configuration",[52,41288,41290],{"id":41289},"correct-format","Correct format",[16,41292,41293],{},"Use full origins with scheme:",[63,41295,41296,41300],{},[66,41297,41298],{},[20,41299,2963],{},[66,41301,41302],{},[20,41303,41304],{},"https:\u002F\u002Fadmin.example.com",[16,41306,7471,41307,41309],{},[1226,41308,7474],{}," omit the scheme.",[52,41311,41313],{"id":41312},"common-mistakes","Common mistakes",[63,41315,41316,41320,41323,41326,41329],{},[66,41317,20107,41318],{},[20,41319,7733],{},[66,41321,41322],{},"trusting too many origins",[66,41324,41325],{},"adding domains that should not be posting to the app",[66,41327,41328],{},"assuming HTTPS behind a proxy automatically requires this setting",[66,41330,41331],{},"forgetting proxy SSL configuration, so Django thinks the request is HTTP and rejects the origin check",[52,41333,41335],{"id":41334},"test-after-deploy","Test after deploy",[16,41337,41338],{},"Use a browser for real CSRF validation:",[63,41340,41341,41344,41347,41350],{},[66,41342,41343],{},"load the admin login page over HTTPS",[66,41345,41346],{},"log in",[66,41348,41349],{},"submit an authenticated form",[66,41351,41352],{},"confirm no 403 CSRF failure occurs",[16,41354,41355],{},"Then inspect logs if it fails:",[106,41357,41358],{"className":108,"code":3266,"language":110,"meta":111,"style":111},[20,41359,41360],{"__ignoreMap":111},[115,41361,41362,41364,41366,41368,41370,41372],{"class":117,"line":118},[115,41363,2785],{"class":262},[115,41365,2788],{"class":202},[115,41367,2791],{"class":132},[115,41369,2794],{"class":202},[115,41371,2797],{"class":202},[115,41373,2800],{"class":202},[16,41375,36485],{},[106,41377,41378],{"className":108,"code":3288,"language":110,"meta":111,"style":111},[20,41379,41380],{"__ignoreMap":111},[115,41381,41382,41384,41386,41388,41390],{"class":117,"line":118},[115,41383,3295],{"class":262},[115,41385,3298],{"class":132},[115,41387,3301],{"class":132},[115,41389,3304],{"class":132},[115,41391,3307],{"class":202},[23099,41393],{},[11,41395,41397],{"id":41396},"_6-set-proxy-and-https-related-settings-correctly","6. Set proxy and HTTPS-related settings correctly",[16,41399,41400],{},"These settings affect both CSRF and secure request handling.",[52,41402,41404],{"id":41403},"secure_proxy_ssl_header",[20,41405,2377],{},[16,41407,41408],{},"If TLS terminates at Nginx, Caddy, or a load balancer and Django receives plain HTTP from that proxy, Django needs a trusted signal for the original scheme.",[106,41410,41411],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,41412,41413],{"__ignoreMap":111},[115,41414,41415,41417,41419,41421,41423,41425,41427],{"class":117,"line":118},[115,41416,2377],{"class":202},[115,41418,2380],{"class":121},[115,41420,2383],{"class":125},[115,41422,2386],{"class":132},[115,41424,1153],{"class":125},[115,41426,2391],{"class":132},[115,41428,2394],{"class":125},[16,41430,41431,41432,41435],{},"Only set this if your proxy actually sends ",[20,41433,41434],{},"X-Forwarded-Proto: https"," and you control that proxy layer. If multiple proxies or load balancers are involved, only trust forwarded headers from your own upstream infrastructure.",[52,41437,41439],{"id":41438},"use_x_forwarded_host",[20,41440,12021],{},[16,41442,41443,41444,211],{},"Leave this off unless your proxy architecture requires Django to trust ",[20,41445,7291],{},[106,41447,41449],{"className":2369,"code":41448,"language":1114,"meta":111,"style":111},"USE_X_FORWARDED_HOST = False\n",[20,41450,41451],{"__ignoreMap":111},[115,41452,41453,41455,41457],{"class":117,"line":118},[115,41454,12021],{"class":202},[115,41456,2380],{"class":121},[115,41458,7355],{"class":202},[16,41460,41461],{},"Enabling it unnecessarily widens the set of upstream headers that influence host validation.",[52,41463,41465,41467],{"id":41464},"secure_ssl_redirect-and-secure-cookies",[20,41466,2407],{}," and secure cookies",[16,41469,41470],{},"If the public site should always use HTTPS:",[106,41472,41473],{"className":2369,"code":2400,"language":1114,"meta":111,"style":111},[20,41474,41475,41483,41491],{"__ignoreMap":111},[115,41476,41477,41479,41481],{"class":117,"line":118},[115,41478,2407],{"class":202},[115,41480,2380],{"class":121},[115,41482,2412],{"class":202},[115,41484,41485,41487,41489],{"class":117,"line":136},[115,41486,2417],{"class":202},[115,41488,2380],{"class":121},[115,41490,2412],{"class":202},[115,41492,41493,41495,41497],{"class":117,"line":149},[115,41494,2426],{"class":202},[115,41496,2380],{"class":121},[115,41498,2412],{"class":202},[16,41500,41501],{},"These settings help keep login and form traffic on HTTPS and prevent insecure cookie transport.",[52,41503,14174],{"id":41504},"verify-4",[106,41506,41507],{"className":108,"code":41108,"language":110,"meta":111,"style":111},[20,41508,41509],{"__ignoreMap":111},[115,41510,41511,41513,41515],{"class":117,"line":118},[115,41512,2764],{"class":262},[115,41514,2767],{"class":202},[115,41516,32984],{"class":132},[16,41518,41519],{},"Check for expected redirects and behavior. Then test login and form submission in a browser, because cookie and CSRF issues usually appear there first.",[23099,41521],{},[11,41523,41525],{"id":41524},"_7-run-the-deployment-verification-checklist","7. Run the deployment verification checklist",[52,41527,41529],{"id":41528},"required-checks","Required checks",[63,41531,41532,41538,41542,41547,41553,41558,41561],{},[66,41533,41534,41535,41537],{},"production ",[20,41536,2713],{}," is unique, non-default, and not committed in code",[66,41539,41540],{},[20,41541,32431],{},[66,41543,41544,41545],{},"explicit ",[20,41546,2719],{},[66,41548,41549,41550,41552],{},"valid ",[20,41551,2725],{}," if needed",[66,41554,2722,41555,41557],{},[20,41556,2377],{}," behind a TLS-terminating proxy",[66,41559,41560],{},"secure cookie settings reviewed",[66,41562,41563],{},"custom error handling and logs confirmed",[52,41565,41567],{"id":41566},"useful-adjacent-checks","Useful adjacent checks",[16,41569,41570],{},"Also review:",[63,41572,41573,41577,41581],{},[66,41574,41575],{},[20,41576,7440],{},[66,41578,41579],{},[20,41580,34549],{},[66,41582,41583],{},[20,41584,34558],{},[52,41586,41588],{"id":41587},"pre-release-commands","Pre-release commands",[106,41590,41591],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,41592,41593],{"__ignoreMap":111},[115,41594,41595,41597,41599,41601],{"class":117,"line":118},[115,41596,1114],{"class":262},[115,41598,1117],{"class":132},[115,41600,1814],{"class":132},[115,41602,1817],{"class":202},[16,41604,41605],{},"If using systemd:",[106,41607,41608],{"className":108,"code":28407,"language":110,"meta":111,"style":111},[20,41609,41610,41620],{"__ignoreMap":111},[115,41611,41612,41614,41616,41618],{"class":117,"line":118},[115,41613,2001],{"class":262},[115,41615,3480],{"class":132},[115,41617,3483],{"class":132},[115,41619,1987],{"class":132},[115,41621,41622,41624,41626,41628,41630],{"class":117,"line":136},[115,41623,2001],{"class":262},[115,41625,3480],{"class":132},[115,41627,1984],{"class":132},[115,41629,2791],{"class":132},[115,41631,2800],{"class":202},[16,41633,41634],{},"If using Docker Compose:",[106,41636,41638],{"className":108,"code":41637,"language":110,"meta":111,"style":111},"docker compose restart web\ndocker compose ps\n",[20,41639,41640,41650],{"__ignoreMap":111},[115,41641,41642,41644,41646,41648],{"class":117,"line":118},[115,41643,3295],{"class":262},[115,41645,3298],{"class":132},[115,41647,3483],{"class":132},[115,41649,3510],{"class":132},[115,41651,41652,41654,41656],{"class":117,"line":136},[115,41653,3295],{"class":262},[115,41655,3298],{"class":132},[115,41657,4790],{"class":132},[52,41659,41661],{"id":41660},"post-release-smoke-tests","Post-release smoke tests",[106,41663,41665],{"className":108,"code":41664,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002F\ncurl -I --resolve bad.example.com:443:server-ip https:\u002F\u002Fbad.example.com\u002F\n",[20,41666,41667,41675],{"__ignoreMap":111},[115,41668,41669,41671,41673],{"class":117,"line":118},[115,41670,2764],{"class":262},[115,41672,2767],{"class":202},[115,41674,32984],{"class":132},[115,41676,41677,41679,41681,41683,41685],{"class":117,"line":136},[115,41678,2764],{"class":262},[115,41680,2767],{"class":202},[115,41682,41139],{"class":202},[115,41684,41142],{"class":132},[115,41686,41145],{"class":132},[16,41688,41689],{},"Then verify manually:",[63,41691,41692,41695,41697,41700,41703],{},[66,41693,41694],{},"homepage loads on the correct domain",[66,41696,1137],{},[66,41698,41699],{},"a form POST succeeds",[66,41701,41702],{},"bad host is rejected",[66,41704,41705],{},"no debug page is visible anywhere",[52,41707,41709],{"id":41708},"recovery","Recovery",[16,41711,41712],{},"If the release breaks host routing or form submissions:",[1173,41714,41715,41718,41721,41724,41727],{},[66,41716,41717],{},"revert the changed environment variables or settings file",[66,41719,41720],{},"redeploy the previous release if needed",[66,41722,41723],{},"restart the app server",[66,41725,41726],{},"confirm the previous service is healthy in logs or process status",[66,41728,41729],{},"re-run the same smoke tests",[11,41731,1321],{"id":1320},[16,41733,41734],{},"This setup works because it aligns Django’s request validation with the way production traffic actually reaches the app.",[63,41736,41737,41742,41747,41752,41757],{},[66,41738,41739,41741],{},[20,41740,2713],{}," protects signed data and must be unique per production deployment",[66,41743,41744,41746],{},[20,41745,32431],{}," removes development-only behavior",[66,41748,41749,41751],{},[20,41750,2719],{}," makes host routing explicit",[66,41753,41754,41756],{},[20,41755,2725],{}," allows trusted browser origins when needed, but only when listed precisely",[66,41758,41759],{},"proxy SSL settings let Django understand whether the original request was HTTPS",[16,41761,41762],{},"The main alternative is settings structure, not behavior. You can use one settings file or split modules, but the production values should still be explicit and reviewable.",[52,41764,41766],{"id":41765},"when-to-automate-this","When to automate this",[16,41768,41769,41770,41773],{},"Once you deploy more than one environment, this checklist should stop being manual. Good first automation targets are environment-variable validation, ",[20,41771,41772],{},"manage.py check --deploy"," in CI, and post-deploy smoke tests for expected host, bad host, admin login, and a CSRF-protected form flow. A reusable production settings template and reverse proxy template also reduce repeated mistakes.",[11,41775,1337],{"id":1336},[63,41777,41778,41784,41790,41795,41804,41813],{},[66,41779,41780,41783],{},[1226,41781,41782],{},"Multiple domains or tenant-style apps:"," keep the host validation strategy explicit. Dynamic host logic can be correct, but it is easier to get wrong than a fixed host list.",[66,41785,41786,41789],{},[1226,41787,41788],{},"Preview environments:"," do not weaken production defaults just to support temporary URLs. Handle preview hosts separately from production.",[66,41791,41792,41794],{},[1226,41793,4397],{}," keep images immutable and inject environment-specific values at runtime through env vars or orchestration config.",[66,41796,41797,41800,41801,41803],{},[1226,41798,41799],{},"Load balancers and health checks:"," some platforms probe with internal hosts or IPs. Only add those to ",[20,41802,2719],{}," if those requests really hit Django and must be accepted.",[66,41805,41806,41809,41810,41812],{},[1226,41807,41808],{},"Static and media files:"," turning off ",[20,41811,7350],{}," also means you should not rely on development-style static serving assumptions in production. Verify your web server or storage setup separately.",[66,41814,41815,41818],{},[1226,41816,41817],{},"Migrations and app restarts:"," a settings change can fail only after restart, so always pair config changes with log inspection and smoke tests.",[11,41820,1386],{"id":1385},[16,41822,41823,41824,211],{},"If you need the broader settings structure behind this checklist, see ",[1395,41825,41826],{"href":3006},"Django settings for production: environment variables, split settings, and secrets",[16,41828,41829,41830,3146,41832,211],{},"For full web server integration, continue with ",[1395,41831,4425],{"href":2985},[1395,41833,41834],{"href":8045},"How to configure HTTPS for Django behind Nginx or Caddy",[16,41836,41837,41838,211],{},"If valid traffic is failing with host errors, use ",[1395,41839,41840],{"href":4445},"How to fix Django 400 Bad Request (Invalid HTTP_HOST header) in production",[16,41842,41843,41844,1153,41846,20346,41848,211],{},"For adjacent production topics, see ",[1395,41845,3000],{"href":2999},[1395,41847,3014],{"href":3013},[1395,41849,34041],{"href":2978},[11,41851,1420],{"id":1419},[52,41853,41855,41856,41858],{"id":41854},"do-i-always-need-csrf_trusted_origins-in-django-production","Do I always need ",[20,41857,2725],{}," in Django production?",[16,41860,41861],{},"No. You need it when Django must trust specific origins for unsafe requests, such as cross-origin form flows or when proxy\u002Fscheme handling causes the request origin seen by Django to differ from the browser origin. If your app works correctly without it, do not add unnecessary origins.",[52,41863,41865,41866,41868],{"id":41864},"what-should-i-put-in-allowed_hosts-behind-nginx-or-a-load-balancer","What should I put in ",[20,41867,2719],{}," behind Nginx or a load balancer?",[16,41870,41871,41872,3146,41874,41876],{},"Usually your public domains only, such as ",[20,41873,3145],{},[20,41875,3149],{},". Add internal hosts only if the proxy or platform truly sends those hosts to Django and they must be accepted.",[52,41878,41880],{"id":41879},"why-does-django-admin-login-fail-with-a-csrf-error-after-enabling-https","Why does Django admin login fail with a CSRF error after enabling HTTPS?",[16,41882,41883],{},"The common causes are:",[63,41885,41886,41892,41896,41901,41904],{},[66,41887,41888,41889,41891],{},"missing or incorrect ",[20,41890,2725],{}," when the browser origin must be explicitly trusted",[66,41893,20107,41894],{},[20,41895,2377],{},[66,41897,41898,41899],{},"proxy not sending ",[20,41900,41434],{},[66,41902,41903],{},"scheme or host mismatch between what the browser uses and what Django sees",[66,41905,41906],{},"cookies not marked secure in an HTTPS-only deployment",[52,41908,35567,41910,41913],{"id":41909},"is-allowed_hosts-ever-acceptable-in-production",[20,41911,41912],{},"ALLOWED_HOSTS = ['*']"," ever acceptable in production?",[16,41915,41916],{},"As a general production setting, no. It disables an important safety check and can hide proxy or routing problems. Use an explicit host list instead.",[52,41918,41920,41921,41923],{"id":41919},"how-do-i-verify-that-debug-is-really-off-after-deployment","How do I verify that ",[20,41922,7350],{}," is really off after deployment?",[16,41925,41926],{},"Request a missing URL or trigger a controlled error path and confirm you do not see a Django traceback page. Then inspect application logs for the real error details.",[1485,41928,41929],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":41931},[41932,41933,41934,41935,41939,41946,41953,41961,41967,41974,41981,41984,41985,41986],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":40342,"depth":136,"text":40343,"children":41936},[41937,41938],{"id":40591,"depth":149,"text":14174},{"id":4955,"depth":149,"text":4956},{"id":40621,"depth":136,"text":41940,"children":41941},"2. Load a real production SECRET_KEY",[41942,41943,41944,41945],{"id":40664,"depth":149,"text":40665},{"id":40673,"depth":149,"text":40674},{"id":40691,"depth":149,"text":14174},{"id":40735,"depth":149,"text":4956},{"id":40743,"depth":136,"text":41947,"children":41948},"3. Set DEBUG = False",[41949,41950,41951],{"id":40793,"depth":149,"text":40665},{"id":40801,"depth":149,"text":14174},{"id":40863,"depth":149,"text":41952},"Failure patterns when DEBUG is left on",{"id":40883,"depth":136,"text":41954,"children":41955},"4. Set ALLOWED_HOSTS explicitly",[41956,41957,41958,41959,41960],{"id":40996,"depth":149,"text":40997},{"id":41019,"depth":149,"text":40674},{"id":41049,"depth":149,"text":41050},{"id":41102,"depth":149,"text":14174},{"id":41151,"depth":149,"text":41152},{"id":41167,"depth":136,"text":41168,"children":41962},[41963,41964,41965,41966],{"id":41271,"depth":149,"text":41272},{"id":41289,"depth":149,"text":41290},{"id":41312,"depth":149,"text":41313},{"id":41334,"depth":149,"text":41335},{"id":41396,"depth":136,"text":41397,"children":41968},[41969,41970,41971,41973],{"id":41403,"depth":149,"text":2377},{"id":41438,"depth":149,"text":12021},{"id":41464,"depth":149,"text":41972},"SECURE_SSL_REDIRECT and secure cookies",{"id":41504,"depth":149,"text":14174},{"id":41524,"depth":136,"text":41525,"children":41975},[41976,41977,41978,41979,41980],{"id":41528,"depth":149,"text":41529},{"id":41566,"depth":149,"text":41567},{"id":41587,"depth":149,"text":41588},{"id":41660,"depth":149,"text":41661},{"id":41708,"depth":149,"text":41709},{"id":1320,"depth":136,"text":1321,"children":41982},[41983],{"id":41765,"depth":149,"text":41766},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":41987},[41988,41990,41992,41993,41995],{"id":41854,"depth":149,"text":41989},"Do I always need CSRF_TRUSTED_ORIGINS in Django production?",{"id":41864,"depth":149,"text":41991},"What should I put in ALLOWED_HOSTS behind Nginx or a load balancer?",{"id":41879,"depth":149,"text":41880},{"id":41909,"depth":149,"text":41994},"Is ALLOWED_HOSTS = ['*'] ever acceptable in production?",{"id":41919,"depth":149,"text":41996},"How do I verify that DEBUG is really off after deployment?","A lot of Django production incidents come from settings.py, not application code.",{},"\u002Fdjango-production-settings-checklist",[2999,3013,2978],{"title":3007,"description":41997},[1557],"django-production-settings-checklist",[1557],"DQ4tM-Daj_B1fJCkj55KWHbgmOxbt2hOO48p4TNM0cQ",{"id":42007,"title":6215,"body":42008,"category":4543,"description":43551,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":43552,"navigation":309,"path":43553,"priority":3094,"related":43554,"role":4552,"section":4553,"seo":43555,"stack":43556,"stem":43558,"tags":43559,"type":4558,"__hash__":43560},"articles\u002Ffix-static-files-not-loading-django.md",{"type":8,"value":42009,"toc":43520},[42010,42012,42018,42028,42031,42042,42045,42047,42050,42073,42076,42078,42082,42084,42105,42108,42128,42131,42133,42145,42155,42157,42161,42164,42263,42266,42299,42305,42322,42325,42328,42373,42376,42378,42384,42389,42403,42406,42427,42432,42455,42462,42467,42480,42482,42503,42511,42514,42516,42520,42523,42527,42657,42660,42676,42679,42701,42704,42716,42720,42759,42762,42788,42791,42793,42797,42800,42803,42893,42895,42909,42912,42928,42930,42942,42948,42951,42954,42956,42960,42963,42966,42985,42988,43003,43006,43009,43011,43015,43018,43024,43118,43121,43137,43139,43181,43184,43187,43189,43193,43196,43199,43211,43214,43260,43262,43281,43284,43297,43302,43304,43307,43327,43330,43349,43352,43366,43372,43374,43383,43385,43429,43431,43434,43454,43456,43460,43466,43472,43478,43482,43485,43489,43501,43507,43517],[11,42011,14],{"id":13},[16,42013,42014,42015,42017],{},"A common production failure is that your Django app loads, but CSS, JavaScript, or Django admin assets are missing. You may see unstyled admin pages, frontend CSS returning 404, or browser console errors for ",[20,42016,24087],{}," URLs after deployment.",[16,42019,42020,42021,42024,42025,42027],{},"This guide is for ",[1226,42022,42023],{},"static files only",": app CSS, JS, build artifacts, and Django admin assets. It is ",[1226,42026,7474],{}," about media files such as user uploads.",[16,42029,42030],{},"This usually appears in one of these production setups:",[63,42032,42033,42036,42039],{},[66,42034,42035],{},"Django + Gunicorn + Nginx",[66,42037,42038],{},"Django + WhiteNoise",[66,42040,42041],{},"Dockerized Django behind Nginx or Caddy",[16,42043,42044],{},"In development, Django serves static files for you. In production, it does not unless you configure an explicit static serving path.",[11,42046,30],{"id":29},[16,42048,42049],{},"If Django static files are not loading in production, the fix is usually one of these:",[1173,42051,42052,42057,42062,42067,42070],{},[66,42053,42054,42055],{},"Set a valid ",[20,42056,11918],{},[66,42058,42059,42060],{},"Run ",[20,42061,13689],{},[66,42063,12356,42064,42066],{},[20,42065,11729],{}," is mapped correctly in Nginx or Caddy, or configure WhiteNoise correctly",[66,42068,42069],{},"Verify the process serving files can read the directory",[66,42071,42072],{},"If using Docker, confirm static files are actually present in the image or mounted volume",[16,42074,42075],{},"Start by checking the exact failing asset URL, then verify Django settings, then confirm the files exist on disk, then verify the serving layer.",[11,42077,43],{"id":42},[11,42079,42081],{"id":42080},"_1-confirm-this-is-a-static-files-issue","1) Confirm this is a static files issue",[16,42083,3155],{},[63,42085,42086,42089,42097],{},[66,42087,42088],{},"Django admin loads without CSS",[66,42090,42091,42092,4493,42094],{},"App CSS or JS returns ",[20,42093,19897],{},[20,42095,42096],{},"403",[66,42098,42099,42100,42102,42103],{},"Static files worked with ",[20,42101,24957],{}," but fail with ",[20,42104,2707],{},[16,42106,42107],{},"Check the browser dev tools Network tab and note:",[63,42109,42110,42116,42125],{},[66,42111,42112,42113],{},"exact asset URL, for example ",[20,42114,42115],{},"\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css",[66,42117,42118,42119,1153,42121,1153,42123],{},"response code: ",[20,42120,19897],{},[20,42122,42096],{},[20,42124,32156],{},[66,42126,42127],{},"content type, especially for CSS and JS",[16,42129,42130],{},"Also confirm this is not a media file problem. Static files are deployment artifacts. Media files are user uploads and are usually stored separately.",[16,42132,3515],{},[106,42134,42135],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,42136,42137],{"__ignoreMap":111},[115,42138,42139,42141,42143],{"class":117,"line":118},[115,42140,2764],{"class":262},[115,42142,2767],{"class":202},[115,42144,13405],{"class":132},[16,42146,42147,42148,42151,42152,211],{},"You want to see ",[20,42149,42150],{},"200 OK"," and an appropriate content type such as ",[20,42153,42154],{},"text\u002Fcss",[23099,42156],{},[11,42158,42160],{"id":42159},"_2-verify-django-static-settings","2) Verify Django static settings",[16,42162,42163],{},"Check your production settings first.",[106,42165,42167],{"className":2369,"code":42166,"language":1114,"meta":111,"style":111},"# settings.py\nDEBUG = False\n\nINSTALLED_APPS = [\n    # ...\n    \"django.contrib.staticfiles\",\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = \"\u002Fsrv\u002Fapp\u002Fstaticfiles\"\n\n# Optional: source asset directories used before collectstatic\nSTATICFILES_DIRS = [\n    BASE_DIR \u002F \"static\",\n]\n",[20,42168,42169,42174,42182,42186,42194,42198,42204,42208,42212,42220,42229,42233,42238,42247,42259],{"__ignoreMap":111},[115,42170,42171],{"class":117,"line":118},[115,42172,42173],{"class":3861},"# settings.py\n",[115,42175,42176,42178,42180],{"class":117,"line":136},[115,42177,7350],{"class":202},[115,42179,2380],{"class":121},[115,42181,7355],{"class":202},[115,42183,42184],{"class":117,"line":149},[115,42185,310],{"emptyLinePlaceholder":309},[115,42187,42188,42190,42192],{"class":117,"line":162},[115,42189,18460],{"class":202},[115,42191,2380],{"class":121},[115,42193,3540],{"class":125},[115,42195,42196],{"class":117,"line":175},[115,42197,16643],{"class":3861},[115,42199,42200,42202],{"class":117,"line":350},[115,42201,18469],{"class":132},[115,42203,3354],{"class":125},[115,42205,42206],{"class":117,"line":365},[115,42207,2552],{"class":125},[115,42209,42210],{"class":117,"line":380},[115,42211,310],{"emptyLinePlaceholder":309},[115,42213,42214,42216,42218],{"class":117,"line":487},[115,42215,11908],{"class":202},[115,42217,2380],{"class":121},[115,42219,11913],{"class":132},[115,42221,42222,42224,42226],{"class":117,"line":2095},[115,42223,11918],{"class":202},[115,42225,2380],{"class":121},[115,42227,42228],{"class":132}," \"\u002Fsrv\u002Fapp\u002Fstaticfiles\"\n",[115,42230,42231],{"class":117,"line":2104},[115,42232,310],{"emptyLinePlaceholder":309},[115,42234,42235],{"class":117,"line":2113},[115,42236,42237],{"class":3861},"# Optional: source asset directories used before collectstatic\n",[115,42239,42240,42243,42245],{"class":117,"line":2122},[115,42241,42242],{"class":202},"STATICFILES_DIRS",[115,42244,2380],{"class":121},[115,42246,3540],{"class":125},[115,42248,42249,42252,42254,42257],{"class":117,"line":2131},[115,42250,42251],{"class":202},"    BASE_DIR",[115,42253,11926],{"class":121},[115,42255,42256],{"class":132}," \"static\"",[115,42258,3354],{"class":125},[115,42260,42261],{"class":117,"line":2136},[115,42262,2552],{"class":125},[16,42264,42265],{},"What to verify:",[63,42267,42268,42273,42279,42284,42289],{},[66,42269,42270,42272],{},[20,42271,32431],{}," in production",[66,42274,42275,42278],{},[20,42276,42277],{},"django.contrib.staticfiles"," is enabled",[66,42280,42281,42283],{},[20,42282,11908],{}," matches the URL prefix your proxy or app uses",[66,42285,42286,42288],{},[20,42287,11918],{}," is a real destination directory for collected files",[66,42290,42291,42293,42294,42296,42297],{},[20,42292,11918],{}," is ",[1226,42295,7474],{}," the same as a source directory in ",[20,42298,42242],{},[16,42300,55,42301,42304],{},[20,42302,42303],{},"findstatic"," to confirm Django can locate files before collection:",[106,42306,42308],{"className":108,"code":42307,"language":110,"meta":111,"style":111},"python manage.py findstatic admin\u002Fcss\u002Fbase.css\n",[20,42309,42310],{"__ignoreMap":111},[115,42311,42312,42314,42316,42319],{"class":117,"line":118},[115,42313,1114],{"class":262},[115,42315,1117],{"class":132},[115,42317,42318],{"class":132}," findstatic",[115,42320,42321],{"class":132}," admin\u002Fcss\u002Fbase.css\n",[16,42323,42324],{},"If this returns nothing, the file is not being found by Django’s staticfiles system.",[16,42326,42327],{},"If you use multiple settings modules, confirm which one is active for the release:",[106,42329,42331],{"className":108,"code":42330,"language":110,"meta":111,"style":111},"python manage.py diffsettings | grep -E 'STATIC_URL|STATIC_ROOT'\nDJANGO_SETTINGS_MODULE=project.settings.production python manage.py diffsettings | grep -E 'STATIC_URL|STATIC_ROOT'\n",[20,42332,42333,42351],{"__ignoreMap":111},[115,42334,42335,42337,42339,42342,42344,42346,42348],{"class":117,"line":118},[115,42336,1114],{"class":262},[115,42338,1117],{"class":132},[115,42340,42341],{"class":132}," diffsettings",[115,42343,579],{"class":121},[115,42345,4838],{"class":262},[115,42347,6482],{"class":202},[115,42349,42350],{"class":132}," 'STATIC_URL|STATIC_ROOT'\n",[115,42352,42353,42355,42357,42359,42361,42363,42365,42367,42369,42371],{"class":117,"line":136},[115,42354,5074],{"class":125},[115,42356,129],{"class":121},[115,42358,16761],{"class":132},[115,42360,19676],{"class":262},[115,42362,1117],{"class":132},[115,42364,42341],{"class":132},[115,42366,579],{"class":121},[115,42368,4838],{"class":262},[115,42370,6482],{"class":202},[115,42372,42350],{"class":132},[16,42374,42375],{},"Rollback note: before changing settings paths, keep a copy of the current config and do not delete any existing working static directory until the new one is verified.",[23099,42377],{},[11,42379,42381,42382],{"id":42380},"_3-run-and-verify-collectstatic","3) Run and verify ",[20,42383,13689],{},[16,42385,42059,42386,42388],{},[20,42387,13689],{}," with the same settings your production app uses.",[106,42390,42391],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,42392,42393],{"__ignoreMap":111},[115,42394,42395,42397,42399,42401],{"class":117,"line":118},[115,42396,1114],{"class":262},[115,42398,1117],{"class":132},[115,42400,1838],{"class":132},[115,42402,1841],{"class":202},[16,42404,42405],{},"If you use a custom settings module:",[106,42407,42409],{"className":108,"code":42408,"language":110,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=project.settings.production python manage.py collectstatic --noinput\n",[20,42410,42411],{"__ignoreMap":111},[115,42412,42413,42415,42417,42419,42421,42423,42425],{"class":117,"line":118},[115,42414,5074],{"class":125},[115,42416,129],{"class":121},[115,42418,16761],{"class":132},[115,42420,19676],{"class":262},[115,42422,1117],{"class":132},[115,42424,1838],{"class":132},[115,42426,1841],{"class":202},[16,42428,42429,42430,241],{},"Then verify files exist in ",[20,42431,11918],{},[106,42433,42435],{"className":108,"code":42434,"language":110,"meta":111,"style":111},"ls -lah \u002Fsrv\u002Fapp\u002Fstaticfiles\nls -lah \u002Fsrv\u002Fapp\u002Fstaticfiles\u002Fadmin\u002Fcss\n",[20,42436,42437,42446],{"__ignoreMap":111},[115,42438,42439,42441,42443],{"class":117,"line":118},[115,42440,532],{"class":262},[115,42442,12216],{"class":202},[115,42444,42445],{"class":132}," \u002Fsrv\u002Fapp\u002Fstaticfiles\n",[115,42447,42448,42450,42452],{"class":117,"line":136},[115,42449,532],{"class":262},[115,42451,12216],{"class":202},[115,42453,42454],{"class":132}," \u002Fsrv\u002Fapp\u002Fstaticfiles\u002Fadmin\u002Fcss\n",[16,42456,42457,42458,42461],{},"You should see collected files, including admin assets if ",[20,42459,42460],{},"django.contrib.admin"," is installed.",[16,42463,6168,42464,42466],{},[20,42465,13689],{}," fails, read the error carefully. Common causes:",[63,42468,42469,42474,42477],{},[66,42470,42471,42472],{},"bad permissions on ",[20,42473,11918],{},[66,42475,42476],{},"missing frontend build output",[66,42478,42479],{},"manifest mismatch when using hashed static storage",[16,42481,3515],{},[106,42483,42485],{"className":108,"code":42484,"language":110,"meta":111,"style":111},"stat \u002Fsrv\u002Fapp\u002Fstaticfiles\npython manage.py findstatic admin\u002Fcss\u002Fbase.css\n",[20,42486,42487,42493],{"__ignoreMap":111},[115,42488,42489,42491],{"class":117,"line":118},[115,42490,31627],{"class":202},[115,42492,42445],{"class":132},[115,42494,42495,42497,42499,42501],{"class":117,"line":136},[115,42496,1114],{"class":262},[115,42498,1117],{"class":132},[115,42500,42318],{"class":132},[115,42502,42321],{"class":132},[16,42504,6168,42505,42507,42508,42510],{},[20,42506,13689],{}," completed but the files are missing from disk, double-check ",[20,42509,11918],{}," and the runtime settings module.",[16,42512,42513],{},"Rollback note: if your deployment uses release directories or symlinks, keep the previous collected static directory available until the new release has passed an asset check. Do not switch traffic to a release that references assets not yet collected.",[23099,42515],{},[11,42517,42519],{"id":42518},"_4-fix-reverse-proxy-static-file-serving","4) Fix reverse proxy static file serving",[16,42521,42522],{},"If Nginx or Caddy serves static files, the request path and server mapping must resolve to the correct filesystem path.",[52,42524,42526],{"id":42525},"nginx-example","Nginx example",[106,42528,42530],{"className":2154,"code":42529,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fapp\u002Fstaticfiles\u002F;\n        access_log off;\n        expires 1d;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,42531,42532,42538,42546,42552,42556,42564,42571,42580,42590,42603,42607,42611,42619,42625,42631,42637,42643,42649,42653],{"__ignoreMap":111},[115,42533,42534,42536],{"class":117,"line":118},[115,42535,2163],{"class":121},[115,42537,2166],{"class":125},[115,42539,42540,42542,42544],{"class":117,"line":136},[115,42541,2171],{"class":121},[115,42543,3808],{"class":202},[115,42545,3811],{"class":125},[115,42547,42548,42550],{"class":117,"line":149},[115,42549,2182],{"class":121},[115,42551,2185],{"class":125},[115,42553,42554],{"class":117,"line":162},[115,42555,310],{"emptyLinePlaceholder":309},[115,42557,42558,42560,42562],{"class":117,"line":175},[115,42559,2214],{"class":121},[115,42561,2217],{"class":262},[115,42563,2220],{"class":125},[115,42565,42566,42568],{"class":117,"line":350},[115,42567,2225],{"class":121},[115,42569,42570],{"class":125},"\u002Fsrv\u002Fapp\u002Fstaticfiles\u002F;\n",[115,42572,42573,42576,42578],{"class":117,"line":365},[115,42574,42575],{"class":121},"        access_log ",[115,42577,7103],{"class":202},[115,42579,3811],{"class":125},[115,42581,42582,42585,42588],{"class":117,"line":380},[115,42583,42584],{"class":121},"        expires ",[115,42586,42587],{"class":202},"1d",[115,42589,3811],{"class":125},[115,42591,42592,42595,42598,42601],{"class":117,"line":487},[115,42593,42594],{"class":121},"        add_header ",[115,42596,42597],{"class":125},"Cache-Control ",[115,42599,42600],{"class":132},"\"public\"",[115,42602,3811],{"class":125},[115,42604,42605],{"class":117,"line":2095},[115,42606,2233],{"class":125},[115,42608,42609],{"class":117,"line":2104},[115,42610,310],{"emptyLinePlaceholder":309},[115,42612,42613,42615,42617],{"class":117,"line":2113},[115,42614,2214],{"class":121},[115,42616,2268],{"class":262},[115,42618,2220],{"class":125},[115,42620,42621,42623],{"class":117,"line":2122},[115,42622,2276],{"class":121},[115,42624,3748],{"class":125},[115,42626,42627,42629],{"class":117,"line":2131},[115,42628,2285],{"class":121},[115,42630,2288],{"class":125},[115,42632,42633,42635],{"class":117,"line":2136},[115,42634,2285],{"class":121},[115,42636,3767],{"class":125},[115,42638,42639,42641],{"class":117,"line":2142},[115,42640,2285],{"class":121},[115,42642,2312],{"class":125},[115,42644,42645,42647],{"class":117,"line":2273},[115,42646,2285],{"class":121},[115,42648,2304],{"class":125},[115,42650,42651],{"class":117,"line":2282},[115,42652,2233],{"class":125},[115,42654,42655],{"class":117,"line":2291},[115,42656,2323],{"class":125},[16,42658,42659],{},"For troubleshooting, the important point is:",[63,42661,42662,42670],{},[66,42663,42664,42666,42667],{},[20,42665,7325],{}," must match ",[20,42668,42669],{},"STATIC_URL = \"\u002Fstatic\u002F\"",[66,42671,42672,42675],{},[20,42673,42674],{},"alias \u002Fsrv\u002Fapp\u002Fstaticfiles\u002F;"," must point to the collected files directory",[16,42677,42678],{},"Test config before reload:",[106,42680,42681],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,42682,42683,42691],{"__ignoreMap":111},[115,42684,42685,42687,42689],{"class":117,"line":118},[115,42686,2001],{"class":262},[115,42688,3906],{"class":132},[115,42690,4282],{"class":202},[115,42692,42693,42695,42697,42699],{"class":117,"line":136},[115,42694,2001],{"class":262},[115,42696,3480],{"class":132},[115,42698,3919],{"class":132},[115,42700,1996],{"class":132},[16,42702,42703],{},"Then test a known asset directly:",[106,42705,42706],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,42707,42708],{"__ignoreMap":111},[115,42709,42710,42712,42714],{"class":117,"line":118},[115,42711,2764],{"class":262},[115,42713,2767],{"class":202},[115,42715,13405],{"class":132},[52,42717,42719],{"id":42718},"caddy-example","Caddy example",[106,42721,42723],{"className":23951,"code":42722,"language":23953,"meta":111,"style":111},"example.com {\n    handle \u002Fstatic\u002F* {\n        root * \u002Fsrv\u002Fapp\u002Fstaticfiles\n        file_server\n    }\n\n    reverse_proxy 127.0.0.1:8000\n}\n",[20,42724,42725,42730,42734,42739,42743,42747,42751,42755],{"__ignoreMap":111},[115,42726,42727],{"class":117,"line":118},[115,42728,42729],{},"example.com {\n",[115,42731,42732],{"class":117,"line":136},[115,42733,24055],{},[115,42735,42736],{"class":117,"line":149},[115,42737,42738],{},"        root * \u002Fsrv\u002Fapp\u002Fstaticfiles\n",[115,42740,42741],{"class":117,"line":162},[115,42742,24065],{},[115,42744,42745],{"class":117,"line":175},[115,42746,2233],{},[115,42748,42749],{"class":117,"line":350},[115,42750,310],{"emptyLinePlaceholder":309},[115,42752,42753],{"class":117,"line":365},[115,42754,24002],{},[115,42756,42757],{"class":117,"line":380},[115,42758,2323],{},[16,42760,42761],{},"Validate and reload:",[106,42763,42764],{"className":108,"code":24447,"language":110,"meta":111,"style":111},[20,42765,42766,42778],{"__ignoreMap":111},[115,42767,42768,42770,42772,42774,42776],{"class":117,"line":118},[115,42769,2001],{"class":262},[115,42771,24106],{"class":132},[115,42773,24109],{"class":132},[115,42775,24112],{"class":202},[115,42777,23945],{"class":132},[115,42779,42780,42782,42784,42786],{"class":117,"line":136},[115,42781,2001],{"class":262},[115,42783,3480],{"class":132},[115,42785,3919],{"class":132},[115,42787,23906],{"class":132},[16,42789,42790],{},"If static files still fail, compare the request URL with the real file path on disk.",[23099,42792],{},[11,42794,42796],{"id":42795},"_5-fix-app-served-static-files-with-whitenoise","5) Fix app-served static files with WhiteNoise",[16,42798,42799],{},"WhiteNoise is appropriate for smaller deployments, single-container setups, or environments where you do not want a separate static file service layer.",[16,42801,42802],{},"Example configuration:",[106,42804,42806],{"className":2369,"code":42805,"language":1114,"meta":111,"style":111},"MIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # ...\n]\n\nSTORAGES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.files.storage.FileSystemStorage\",\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    },\n}\n",[20,42807,42808,42816,42822,42828,42832,42836,42840,42848,42854,42865,42869,42875,42885,42889],{"__ignoreMap":111},[115,42809,42810,42812,42814],{"class":117,"line":118},[115,42811,16617],{"class":202},[115,42813,2380],{"class":121},[115,42815,3540],{"class":125},[115,42817,42818,42820],{"class":117,"line":136},[115,42819,16627],{"class":132},[115,42821,3354],{"class":125},[115,42823,42824,42826],{"class":117,"line":149},[115,42825,16635],{"class":132},[115,42827,3354],{"class":125},[115,42829,42830],{"class":117,"line":162},[115,42831,16643],{"class":3861},[115,42833,42834],{"class":117,"line":175},[115,42835,2552],{"class":125},[115,42837,42838],{"class":117,"line":350},[115,42839,310],{"emptyLinePlaceholder":309},[115,42841,42842,42844,42846],{"class":117,"line":365},[115,42843,16659],{"class":202},[115,42845,2380],{"class":121},[115,42847,2166],{"class":125},[115,42849,42850,42852],{"class":117,"line":380},[115,42851,10664],{"class":132},[115,42853,3374],{"class":125},[115,42855,42856,42858,42860,42863],{"class":117,"line":487},[115,42857,16677],{"class":132},[115,42859,2513],{"class":125},[115,42861,42862],{"class":132},"\"django.core.files.storage.FileSystemStorage\"",[115,42864,3354],{"class":125},[115,42866,42867],{"class":117,"line":2095},[115,42868,3403],{"class":125},[115,42870,42871,42873],{"class":117,"line":2104},[115,42872,16669],{"class":132},[115,42874,3374],{"class":125},[115,42876,42877,42879,42881,42883],{"class":117,"line":2113},[115,42878,16677],{"class":132},[115,42880,2513],{"class":125},[115,42882,16682],{"class":132},[115,42884,3354],{"class":125},[115,42886,42887],{"class":117,"line":2122},[115,42888,3403],{"class":125},[115,42890,42891],{"class":117,"line":2131},[115,42892,2323],{"class":125},[16,42894,11310],{},[106,42896,42897],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,42898,42899],{"__ignoreMap":111},[115,42900,42901,42903,42905,42907],{"class":117,"line":118},[115,42902,1114],{"class":262},[115,42904,1117],{"class":132},[115,42906,1838],{"class":132},[115,42908,1841],{"class":202},[16,42910,42911],{},"Common WhiteNoise failure cases:",[63,42913,42914,42917,42920,42925],{},[66,42915,42916],{},"middleware missing",[66,42918,42919],{},"middleware in the wrong place",[66,42921,42922,42923],{},"frontend assets were not built before ",[20,42924,13689],{},[66,42926,42927],{},"manifest storage references files that do not exist",[16,42929,3515],{},[106,42931,42932],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,42933,42934],{"__ignoreMap":111},[115,42935,42936,42938,42940],{"class":117,"line":118},[115,42937,2764],{"class":262},[115,42939,2767],{"class":202},[115,42941,13405],{"class":132},[16,42943,42944,42945,42947],{},"If Gunicorn is serving the app and WhiteNoise is configured correctly, static requests should return ",[20,42946,17741],{}," without relying on Nginx file mapping.",[16,42949,42950],{},"After changing middleware or storage settings, reload or restart the application process before retesting.",[16,42952,42953],{},"Critical deploy note: if you use manifest-based static storage, deploy the application code and collected static files together. A partial deploy or rollback that changes templates or asset references without updating static files can break CSS or JS loading.",[23099,42955],{},[11,42957,42959],{"id":42958},"_6-check-file-permissions-and-ownership","6) Check file permissions and ownership",[16,42961,42962],{},"Even with correct paths, the serving process must be able to read the files.",[16,42964,42965],{},"Inspect directory traversal permissions:",[106,42967,42969],{"className":108,"code":42968,"language":110,"meta":111,"style":111},"namei -l \u002Fsrv\u002Fapp\u002Fstaticfiles\nstat \u002Fsrv\u002Fapp\u002Fstaticfiles\n",[20,42970,42971,42979],{"__ignoreMap":111},[115,42972,42973,42975,42977],{"class":117,"line":118},[115,42974,31618],{"class":262},[115,42976,14881],{"class":202},[115,42978,42445],{"class":132},[115,42980,42981,42983],{"class":117,"line":136},[115,42982,31627],{"class":202},[115,42984,42445],{"class":132},[16,42986,42987],{},"Things to verify:",[63,42989,42990,42997,43000],{},[66,42991,42992,42993,42996],{},"each parent directory is executable (",[20,42994,42995],{},"x",") so it can be traversed",[66,42998,42999],{},"files are readable by the process user or group",[66,43001,43002],{},"mounted volumes in Docker are present and readable",[16,43004,43005],{},"If using Nginx, make sure the Nginx worker user can read the directory. If using WhiteNoise, the app process user must be able to read it.",[16,43007,43008],{},"SELinux can also block access on some systems, but treat that as an edge case after path and permission checks.",[23099,43010],{},[11,43012,43014],{"id":43013},"_7-check-docker-and-cicd-specific-causes","7) Check Docker and CI\u002FCD-specific causes",[16,43016,43017],{},"In containerized deployments, static file problems often come from build ordering or runtime mounts.",[16,43019,43020,43021,241],{},"Example Dockerfile pattern aligned with ",[20,43022,43023],{},"STATIC_ROOT = \"\u002Fsrv\u002Fapp\u002Fstaticfiles\"",[106,43025,43027],{"className":16832,"code":43026,"language":16834,"meta":111,"style":111},"FROM python:3.12-slim\n\nWORKDIR \u002Fsrv\u002Fapp\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nENV DJANGO_SETTINGS_MODULE=project.settings.production\n\nRUN python manage.py collectstatic --noinput\n\nCMD [\"gunicorn\", \"project.wsgi:application\", \"--bind\", \"0.0.0.0:8000\"]\n",[20,43028,43029,43035,43039,43046,43050,43056,43062,43066,43072,43076,43082,43086,43092,43096],{"__ignoreMap":111},[115,43030,43031,43033],{"class":117,"line":118},[115,43032,11089],{"class":121},[115,43034,16843],{"class":125},[115,43036,43037],{"class":117,"line":136},[115,43038,310],{"emptyLinePlaceholder":309},[115,43040,43041,43043],{"class":117,"line":149},[115,43042,16885],{"class":121},[115,43044,43045],{"class":125}," \u002Fsrv\u002Fapp\n",[115,43047,43048],{"class":117,"line":162},[115,43049,310],{"emptyLinePlaceholder":309},[115,43051,43052,43054],{"class":117,"line":175},[115,43053,16897],{"class":121},[115,43055,16900],{"class":125},[115,43057,43058,43060],{"class":117,"line":350},[115,43059,16905],{"class":121},[115,43061,16908],{"class":125},[115,43063,43064],{"class":117,"line":365},[115,43065,310],{"emptyLinePlaceholder":309},[115,43067,43068,43070],{"class":117,"line":380},[115,43069,16897],{"class":121},[115,43071,16919],{"class":125},[115,43073,43074],{"class":117,"line":487},[115,43075,310],{"emptyLinePlaceholder":309},[115,43077,43078,43080],{"class":117,"line":2095},[115,43079,16852],{"class":121},[115,43081,16869],{"class":125},[115,43083,43084],{"class":117,"line":2104},[115,43085,310],{"emptyLinePlaceholder":309},[115,43087,43088,43090],{"class":117,"line":2113},[115,43089,16905],{"class":121},[115,43091,16930],{"class":125},[115,43093,43094],{"class":117,"line":2122},[115,43095,310],{"emptyLinePlaceholder":309},[115,43097,43098,43100,43102,43104,43106,43108,43110,43112,43114,43116],{"class":117,"line":2131},[115,43099,16939],{"class":121},[115,43101,7493],{"class":125},[115,43103,16944],{"class":132},[115,43105,1153],{"class":125},[115,43107,16969],{"class":132},[115,43109,1153],{"class":125},[115,43111,16949],{"class":132},[115,43113,1153],{"class":125},[115,43115,16954],{"class":132},[115,43117,2552],{"class":125},[16,43119,43120],{},"Common failures:",[63,43122,43123,43128,43131,43134],{},[66,43124,43125,43127],{},[20,43126,13689],{}," ran before app files were copied",[66,43129,43130],{},"multi-stage build omitted the generated static directory",[66,43132,43133],{},"container starts with an empty mounted volume replacing image contents",[66,43135,43136],{},"release references hashed asset names not present in the mounted static volume",[16,43138,17389],{},[106,43140,43142],{"className":108,"code":43141,"language":110,"meta":111,"style":111},"docker exec -it \u003Ccontainer> ls -lah \u002Fsrv\u002Fapp\u002Fstaticfiles\ndocker logs \u003Ccontainer>\n",[20,43143,43144,43167],{"__ignoreMap":111},[115,43145,43146,43148,43150,43153,43155,43157,43159,43161,43163,43165],{"class":117,"line":118},[115,43147,3295],{"class":262},[115,43149,5258],{"class":132},[115,43151,43152],{"class":202}," -it",[115,43154,7691],{"class":121},[115,43156,31275],{"class":132},[115,43158,31278],{"class":125},[115,43160,22818],{"class":121},[115,43162,12758],{"class":132},[115,43164,12216],{"class":202},[115,43166,42445],{"class":132},[115,43168,43169,43171,43173,43175,43177,43179],{"class":117,"line":136},[115,43170,3295],{"class":262},[115,43172,3301],{"class":132},[115,43174,7691],{"class":121},[115,43176,31275],{"class":132},[115,43178,31278],{"class":125},[115,43180,17380],{"class":121},[16,43182,43183],{},"If you use a shared volume for static files, confirm the current release and the collected files match.",[16,43185,43186],{},"Rollback note: when rolling back a container image, make sure the static volume or mounted directory also matches that release. Code from one release and manifest-hashed assets from another can break pages even if both exist.",[23099,43188],{},[11,43190,43192],{"id":43191},"_8-verify-the-fix-end-to-end","8) Verify the fix end to end",[16,43194,43195],{},"After any change, test the full path.",[16,43197,43198],{},"Check a known asset directly:",[106,43200,43201],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,43202,43203],{"__ignoreMap":111},[115,43204,43205,43207,43209],{"class":117,"line":118},[115,43206,2764],{"class":262},[115,43208,2767],{"class":202},[115,43210,13405],{"class":132},[16,43212,43213],{},"Then inspect logs:",[106,43215,43217],{"className":108,"code":43216,"language":110,"meta":111,"style":111},"journalctl -u nginx --since \"10 minutes ago\" || sudo tail -n 200 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\ndocker logs \u003Ccontainer>\n",[20,43218,43219,43246],{"__ignoreMap":111},[115,43220,43221,43223,43225,43227,43230,43233,43236,43238,43240,43242,43244],{"class":117,"line":118},[115,43222,2785],{"class":262},[115,43224,2788],{"class":202},[115,43226,3906],{"class":132},[115,43228,43229],{"class":202}," --since",[115,43231,43232],{"class":132}," \"10 minutes ago\"",[115,43234,43235],{"class":121}," ||",[115,43237,14228],{"class":262},[115,43239,13188],{"class":132},[115,43241,2794],{"class":202},[115,43243,31219],{"class":202},[115,43245,13195],{"class":132},[115,43247,43248,43250,43252,43254,43256,43258],{"class":117,"line":136},[115,43249,3295],{"class":262},[115,43251,3301],{"class":132},[115,43253,7691],{"class":121},[115,43255,31275],{"class":132},[115,43257,31278],{"class":125},[115,43259,17380],{"class":121},[16,43261,31301],{},[63,43263,43264,43270,43273,43278],{},[66,43265,43266,25116,43268],{},[20,43267,19897],{},[20,43269,24087],{},[66,43271,43272],{},"permission denied errors",[66,43274,43275,43276],{},"startup errors from ",[20,43277,13689],{},[66,43279,43280],{},"wrong content type for CSS or JS",[16,43282,43283],{},"Also rule out cache effects:",[63,43285,43286,43289,43294],{},[66,43287,43288],{},"hard refresh browser",[66,43290,43291,43292],{},"test with ",[20,43293,2764],{},[66,43295,43296],{},"purge CDN cache if you use one",[16,43298,43299,43300,211],{},"Do not remove the previous static directory or previous release artifact until the new asset URL checks return ",[20,43301,17741],{},[11,43303,1321],{"id":1320},[16,43305,43306],{},"This works because Django’s production static workflow has two separate parts:",[1173,43308,43309,43319],{},[66,43310,43311,2513,43314,43316,43317],{},[1226,43312,43313],{},"collection",[20,43315,13689],{}," gathers files into ",[20,43318,11918],{},[66,43320,43321,43324,43325],{},[1226,43322,43323],{},"serving",": Nginx, Caddy, or WhiteNoise serves those files at ",[20,43326,11908],{},[16,43328,43329],{},"Most failures happen because one side is correct and the other is not. For example:",[63,43331,43332,43337,43343,43346],{},[66,43333,43334,43336],{},[20,43335,13689],{}," succeeded, but Nginx points to the wrong directory",[66,43338,43339,43340,43342],{},"Nginx is correct, but ",[20,43341,11918],{}," is empty",[66,43344,43345],{},"WhiteNoise is enabled, but assets were never collected",[66,43347,43348],{},"Docker image has static files, but a runtime volume hides them",[16,43350,43351],{},"Choose one clear strategy:",[63,43353,43354,43360],{},[66,43355,43356,43359],{},[1226,43357,43358],{},"Nginx\u002FCaddy serves static files"," for common VM or VPS deployments",[66,43361,43362,43365],{},[1226,43363,43364],{},"WhiteNoise serves static files"," for simpler app-hosted deployments",[16,43367,43368,43369,43371],{},"Using Nginx for ",[20,43370,11729],{}," while WhiteNoise is also installed is not automatically broken, but it often makes troubleshooting less clear. Pick one serving path you actually rely on and verify it end to end.",[52,43373,41766],{"id":41765},[16,43375,43376,43377,43379,43380,43382],{},"If every deploy requires manual ",[20,43378,13689],{},", proxy validation, and asset smoke testing, this is a good place to introduce a reusable script or deployment template. The first parts worth automating are ",[20,43381,13689],{},", config validation, and a post-deploy check against one known static asset URL. That reduces avoidable release mistakes without changing your application architecture.",[11,43384,1337],{"id":1336},[63,43386,43387,43396,43405,43411,43417,43423],{},[66,43388,43389,43392,43393,43395],{},[1226,43390,43391],{},"Hashed frontend assets",": if your frontend build produces hashed filenames, build assets before ",[20,43394,13689],{},". Otherwise manifest-based storage may reference missing files.",[66,43397,43398,43401,43402,211],{},[1226,43399,43400],{},"Static vs media",": do not use this guide to fix user-uploaded files. That is a separate storage path and serving setup. See ",[1395,43403,43404],{"href":2978},"Django static files vs media files in production",[66,43406,43407,43410],{},[1226,43408,43409],{},"Development vs production",": the Django development server serves static files automatically. Production does not.",[66,43412,43413,43416],{},[1226,43414,43415],{},"CDN or object storage",": valid production architectures, but outside the main troubleshooting path here.",[66,43418,43419,43422],{},[1226,43420,43421],{},"Proxy\u002FTLS headers",": usually unrelated to static 404s, but incorrect proxy configuration can still affect app behavior elsewhere.",[66,43424,43425,43428],{},[1226,43426,43427],{},"Migrations",": database migrations do not fix static file issues, but make sure your release process handles both separately.",[11,43430,1386],{"id":1385},[16,43432,43433],{},"For related deployment patterns, see:",[63,43435,43436,43440,43444,43450],{},[66,43437,43438],{},[1395,43439,43404],{"href":2978},[66,43441,43442],{},[1395,43443,4425],{"href":2985},[66,43445,43446],{},[1395,43447,43449],{"href":43448},"\u002Fdeploy\u002Fserve-static-files-whitenoise-django","Serve Django static files with WhiteNoise",[66,43451,43452],{},[1395,43453,3000],{"href":2999},[11,43455,1420],{"id":1419},[52,43457,43459],{"id":43458},"why-do-django-static-files-load-locally-but-not-in-production","Why do Django static files load locally but not in production?",[16,43461,43462,43463,43465],{},"Because the development server serves static files automatically, while production requires an explicit strategy. You must collect files into ",[20,43464,11918],{}," and serve them through Nginx, Caddy, or WhiteNoise.",[52,43467,43469,43470,28849],{"id":43468},"do-i-need-to-run-collectstatic-on-every-deploy","Do I need to run ",[20,43471,13689],{},[16,43473,43474,43475,43477],{},"Usually yes, if static assets can change between releases. A safe deployment process runs ",[20,43476,13689],{}," during build or release and verifies at least one known asset URL before marking the deploy healthy.",[52,43479,43481],{"id":43480},"should-nginx-serve-django-static-files-or-should-i-use-whitenoise","Should Nginx serve Django static files or should I use WhiteNoise?",[16,43483,43484],{},"Use Nginx or Caddy when you already have a reverse proxy and want a conventional production setup. Use WhiteNoise for simpler deployments, especially single-container apps, where keeping static serving inside the app process is acceptable.",[52,43486,43488],{"id":43487},"why-is-django-admin-missing-css-in-production","Why is Django admin missing CSS in production?",[16,43490,43491,43492,43494,43495,43497,43498,43500],{},"That usually means one of three things: ",[20,43493,13689],{}," was not run, ",[20,43496,11729],{}," is mapped to the wrong directory, or the serving process cannot read the collected files. Test ",[20,43499,42115],{}," directly to narrow it down.",[52,43502,41920,43504,43506],{"id":43503},"how-do-i-verify-that-static-is-mapped-to-the-correct-directory",[20,43505,11729],{}," is mapped to the correct directory?",[16,43508,43509,43510,43512,43513,43516],{},"Check the URL being requested, then compare it to the real file path on disk. For example, if ",[20,43511,42115],{}," is requested, the file should exist under your collected static directory, such as ",[20,43514,43515],{},"\u002Fsrv\u002Fapp\u002Fstaticfiles\u002Fadmin\u002Fcss\u002Fbase.css",", and your proxy or WhiteNoise setup must serve that path successfully.",[1485,43518,43519],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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}",{"title":111,"searchDepth":149,"depth":149,"links":43521},[43522,43523,43524,43525,43526,43527,43529,43533,43534,43535,43536,43537,43540,43541,43542],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":42080,"depth":136,"text":42081},{"id":42159,"depth":136,"text":42160},{"id":42380,"depth":136,"text":43528},"3) Run and verify collectstatic",{"id":42518,"depth":136,"text":42519,"children":43530},[43531,43532],{"id":42525,"depth":149,"text":42526},{"id":42718,"depth":149,"text":42719},{"id":42795,"depth":136,"text":42796},{"id":42958,"depth":136,"text":42959},{"id":43013,"depth":136,"text":43014},{"id":43191,"depth":136,"text":43192},{"id":1320,"depth":136,"text":1321,"children":43538},[43539],{"id":41765,"depth":149,"text":41766},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":43543},[43544,43545,43547,43548,43549],{"id":43458,"depth":149,"text":43459},{"id":43468,"depth":149,"text":43546},"Do I need to run collectstatic on every deploy?",{"id":43480,"depth":149,"text":43481},{"id":43487,"depth":149,"text":43488},{"id":43503,"depth":149,"text":43550},"How do I verify that \u002Fstatic\u002F is mapped to the correct directory?","A common production failure is that your Django app loads, but CSS, JavaScript, or Django admin assets are missing.",{},"\u002Ffix-static-files-not-loading-django",[4455,32346,4445],{"title":6215,"description":43551},[1557,2156,43557],"whitenoise","fix-static-files-not-loading-django",[1557,2156,43557],"CXgfb6rs9RcdrdN2452jCuc7t2wQAI8aTec_GB8YBH4",{"id":43562,"title":34041,"body":43563,"category":3088,"description":44732,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":44733,"navigation":309,"path":44734,"priority":35628,"related":44735,"role":3097,"section":3098,"seo":44737,"stack":44738,"stem":44739,"tags":44740,"type":3104,"__hash__":44741},"articles\u002Fdjango-static-vs-media-files-production.md",{"type":8,"value":43564,"toc":44707},[43565,43567,43570,43573,43595,43598,43600,43607,43622,43625,43627,43631,43634,43735,43738,43755,43757,43775,43778,43782,43785,43791,43794,43808,43814,43816,43839,43846,43849,43863,43866,43869,43880,43882,43913,43916,43928,43932,43935,43939,43947,43950,43961,43964,43987,43990,44027,44030,44034,44037,44040,44207,44210,44239,44242,44264,44266,44295,44298,44302,44305,44316,44319,44399,44405,44408,44419,44422,44425,44436,44440,44443,44446,44460,44463,44474,44477,44481,44484,44487,44501,44513,44515,44522,44536,44539,44556,44559,44565,44567,44633,44635,44640,44645,44650,44655,44657,44665,44676,44680,44683,44687,44690,44694,44697,44701,44704],[11,43566,14],{"id":13},[16,43568,43569],{},"Many Django deployments break because static files and media files are treated as the same thing. In development, Django can make both seem simple. In production, they have different lifecycles, permissions, storage rules, caching behavior, and backup requirements.",[16,43571,43572],{},"If you mix them together, common failures follow:",[63,43574,43575,43578,43581,43586,43589,43592],{},[66,43576,43577],{},"CSS and JavaScript do not load after deploy",[66,43579,43580],{},"user uploads disappear on release cleanup",[66,43582,43583,43585],{},[20,43584,13689],{}," overwrites or collides with uploaded files",[66,43587,43588],{},"containers lose uploads on restart",[66,43590,43591],{},"reverse proxy rules expose files incorrectly",[66,43593,43594],{},"app processes get unnecessary write access to asset directories",[16,43596,43597],{},"A safe production setup separates build-time assets from runtime user data.",[11,43599,30],{"id":29},[16,43601,43602,43603,43606],{},"For ",[1226,43604,43605],{},"Django static vs media files in production",", the rule is simple:",[63,43608,43609,43617],{},[66,43610,43611,43613,43614,43616],{},[1226,43612,24716],{}," are application assets such as CSS, JavaScript, Django admin assets, and bundled frontend files. They are collected during deploy with ",[20,43615,13689],{},", served read-only, and can be rebuilt from source.",[66,43618,43619,43621],{},[1226,43620,24722],{}," are user-uploaded or runtime-generated files. They must be stored separately, remain writable by the app where needed, survive redeploys, and be included in backup and recovery plans.",[16,43623,43624],{},"In normal production traffic, Django should not serve either directly. Use Nginx, Caddy, object storage, or another dedicated file-serving path.",[11,43626,43],{"id":42},[11,43628,43630],{"id":43629},"_1-define-static-and-media-separately-in-django-settings","1. Define static and media separately in Django settings",[16,43632,43633],{},"Use distinct URLs and filesystem paths.",[106,43635,43637],{"className":2369,"code":43636,"language":1114,"meta":111,"style":111},"# settings.py\nfrom pathlib import Path\nimport os\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = os.environ.get(\"DJANGO_STATIC_ROOT\", \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\")\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = os.environ.get(\"DJANGO_MEDIA_ROOT\", \"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\")\n",[20,43638,43639,43643,43653,43659,43663,43675,43679,43687,43705,43709,43717],{"__ignoreMap":111},[115,43640,43641],{"class":117,"line":118},[115,43642,42173],{"class":3861},[115,43644,43645,43647,43649,43651],{"class":117,"line":136},[115,43646,5621],{"class":121},[115,43648,16353],{"class":125},[115,43650,5613],{"class":121},[115,43652,16358],{"class":125},[115,43654,43655,43657],{"class":117,"line":149},[115,43656,5613],{"class":121},[115,43658,5616],{"class":125},[115,43660,43661],{"class":117,"line":162},[115,43662,310],{"emptyLinePlaceholder":309},[115,43664,43665,43667,43669,43671,43673],{"class":117,"line":175},[115,43666,16374],{"class":202},[115,43668,2380],{"class":121},[115,43670,16379],{"class":125},[115,43672,16382],{"class":202},[115,43674,34339],{"class":125},[115,43676,43677],{"class":117,"line":350},[115,43678,310],{"emptyLinePlaceholder":309},[115,43680,43681,43683,43685],{"class":117,"line":365},[115,43682,11908],{"class":202},[115,43684,2380],{"class":121},[115,43686,11913],{"class":132},[115,43688,43689,43691,43693,43695,43698,43700,43703],{"class":117,"line":380},[115,43690,11918],{"class":202},[115,43692,2380],{"class":121},[115,43694,8884],{"class":125},[115,43696,43697],{"class":132},"\"DJANGO_STATIC_ROOT\"",[115,43699,1153],{"class":125},[115,43701,43702],{"class":132},"\"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\"",[115,43704,2394],{"class":125},[115,43706,43707],{"class":117,"line":487},[115,43708,310],{"emptyLinePlaceholder":309},[115,43710,43711,43713,43715],{"class":117,"line":2095},[115,43712,15204],{"class":202},[115,43714,2380],{"class":121},[115,43716,15209],{"class":132},[115,43718,43719,43721,43723,43725,43728,43730,43733],{"class":117,"line":2104},[115,43720,15214],{"class":202},[115,43722,2380],{"class":121},[115,43724,8884],{"class":125},[115,43726,43727],{"class":132},"\"DJANGO_MEDIA_ROOT\"",[115,43729,1153],{"class":125},[115,43731,43732],{"class":132},"\"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\"",[115,43734,2394],{"class":125},[16,43736,43737],{},"Production rules:",[63,43739,43740,43747,43752],{},[66,43741,43742,43744,43745],{},[20,43743,11918],{}," is the output directory for ",[20,43746,13689],{},[66,43748,43749,43751],{},[20,43750,15214],{}," is the runtime storage directory for uploads",[66,43753,43754],{},"these paths must not overlap",[16,43756,8572],{},[106,43758,43760],{"className":108,"code":43759,"language":110,"meta":111,"style":111},"python manage.py shell -c \"from django.conf import settings; print(settings.STATIC_ROOT); print(settings.MEDIA_ROOT)\"\n",[20,43761,43762],{"__ignoreMap":111},[115,43763,43764,43766,43768,43770,43772],{"class":117,"line":118},[115,43765,1114],{"class":262},[115,43767,1117],{"class":132},[115,43769,9071],{"class":132},[115,43771,1024],{"class":202},[115,43773,43774],{"class":132}," \"from django.conf import settings; print(settings.STATIC_ROOT); print(settings.MEDIA_ROOT)\"\n",[16,43776,43777],{},"Rollback note: if you already use one shared directory for both, separate them before the next deploy. Do not move files blindly in production without backing up current uploads first.",[11,43779,43781],{"id":43780},"_2-use-a-directory-layout-that-survives-releases","2. Use a directory layout that survives releases",[16,43783,43784],{},"A common non-Docker Linux layout looks like this:",[106,43786,43789],{"className":43787,"code":43788,"language":247,"meta":111},[245],"\u002Fvar\u002Fwww\u002Fmyapp\u002Freleases\u002F2026-04-24\u002F\n\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F\n\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F\n",[20,43790,43788],{"__ignoreMap":111},[16,43792,43793],{},"Recommended pattern:",[63,43795,43796,43802,43805],{},[66,43797,43798,43799],{},"release code lives under ",[20,43800,43801],{},"releases\u002F...",[66,43803,43804],{},"static files are collected into a shared static directory or a release-specific static directory that your proxy points to",[66,43806,43807],{},"media lives in a shared persistent directory outside release cleanup",[16,43809,43810,43811,211],{},"If you use symlink-based releases, make sure deploy cleanup only removes old release directories, not ",[20,43812,43813],{},"shared\u002Fmedia",[16,43815,8572],{},[106,43817,43819],{"className":108,"code":43818,"language":110,"meta":111,"style":111},"ls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F\nls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F\n",[20,43820,43821,43830],{"__ignoreMap":111},[115,43822,43823,43825,43827],{"class":117,"line":118},[115,43824,532],{"class":262},[115,43826,12216],{"class":202},[115,43828,43829],{"class":132}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F\n",[115,43831,43832,43834,43836],{"class":117,"line":136},[115,43833,532],{"class":262},[115,43835,12216],{"class":202},[115,43837,43838],{"class":132}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F\n",[11,43840,43842,43843,43845],{"id":43841},"_3-run-collectstatic-during-deploy-not-ad-hoc","3. Run ",[20,43844,13689],{}," during deploy, not ad hoc",[16,43847,43848],{},"Static assets should be built as part of the release process.",[106,43850,43851],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,43852,43853],{"__ignoreMap":111},[115,43854,43855,43857,43859,43861],{"class":117,"line":118},[115,43856,1114],{"class":262},[115,43858,1117],{"class":132},[115,43860,1838],{"class":132},[115,43862,1841],{"class":202},[16,43864,43865],{},"Run this in CI, a release script, or a controlled deploy step after code is in place and before switching traffic.",[16,43867,43868],{},"Why:",[63,43870,43871,43874,43877],{},[66,43872,43873],{},"static output must match the current app version",[66,43875,43876],{},"admin assets and frontend bundles need a predictable location",[66,43878,43879],{},"you avoid manual production drift",[16,43881,8572],{},[106,43883,43885],{"className":108,"code":43884,"language":110,"meta":111,"style":111},"ls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F\nfind \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002Fadmin -maxdepth 2 | head\n",[20,43886,43887,43895],{"__ignoreMap":111},[115,43888,43889,43891,43893],{"class":117,"line":118},[115,43890,532],{"class":262},[115,43892,12216],{"class":202},[115,43894,43829],{"class":132},[115,43896,43897,43900,43903,43906,43909,43911],{"class":117,"line":136},[115,43898,43899],{"class":262},"find",[115,43901,43902],{"class":132}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002Fadmin",[115,43904,43905],{"class":202}," -maxdepth",[115,43907,43908],{"class":202}," 2",[115,43910,579],{"class":121},[115,43912,582],{"class":262},[16,43914,43915],{},"Browser or HTTP check:",[106,43917,43918],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,43919,43920],{"__ignoreMap":111},[115,43921,43922,43924,43926],{"class":117,"line":118},[115,43923,2764],{"class":262},[115,43925,2767],{"class":202},[115,43927,13405],{"class":132},[16,43929,23630,43930,211],{},[20,43931,42150],{},[16,43933,43934],{},"Rollback note: if a release changes asset names or manifest output, keep the previous release available until the new static files are verified. If needed, switch traffic back to the previous app version and previous static mapping together.",[11,43936,43938],{"id":43937},"_4-keep-media-writable-and-persistent","4. Keep media writable and persistent",[16,43940,43941,43942,43944,43945,211],{},"Media files are runtime data. The application may need write access to ",[20,43943,15214],{},", but it should not need write access to ",[20,43946,11918],{},[16,43948,43949],{},"Example permission approach:",[63,43951,43952,43958],{},[66,43953,43954,43955],{},"app user can write to ",[20,43956,43957],{},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia",[66,43959,43960],{},"app user cannot modify static files after deploy if you can avoid it",[16,43962,43963],{},"Check path permissions:",[106,43965,43967],{"className":108,"code":43966,"language":110,"meta":111,"style":111},"namei -l \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\nnamei -l \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\n",[20,43968,43969,43978],{"__ignoreMap":111},[115,43970,43971,43973,43975],{"class":117,"line":118},[115,43972,31618],{"class":262},[115,43974,14881],{"class":202},[115,43976,43977],{"class":132}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\n",[115,43979,43980,43982,43984],{"class":117,"line":136},[115,43981,31618],{"class":262},[115,43983,14881],{"class":202},[115,43985,43986],{"class":132}," \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\n",[16,43988,43989],{},"Upload a test file through the application, then verify it exists:",[106,43991,43993],{"className":108,"code":43992,"language":110,"meta":111,"style":111},"ls -lah \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F\n# Replace with the real uploaded file URL returned by your app\ncurl -I https:\u002F\u002Fexample.com\u002Fmedia\u002F\u003Cactual-upload-path>\n",[20,43994,43995,44003,44008],{"__ignoreMap":111},[115,43996,43997,43999,44001],{"class":117,"line":118},[115,43998,532],{"class":262},[115,44000,12216],{"class":202},[115,44002,43838],{"class":132},[115,44004,44005],{"class":117,"line":136},[115,44006,44007],{"class":3861},"# Replace with the real uploaded file URL returned by your app\n",[115,44009,44010,44012,44014,44017,44019,44022,44025],{"class":117,"line":149},[115,44011,2764],{"class":262},[115,44013,2767],{"class":202},[115,44015,44016],{"class":132}," https:\u002F\u002Fexample.com\u002Fmedia\u002F",[115,44018,22810],{"class":121},[115,44020,44021],{"class":132},"actual-upload-pat",[115,44023,44024],{"class":125},"h",[115,44026,17380],{"class":121},[16,44028,44029],{},"Restart or redeploy the app and confirm the file still exists. If it disappears, your media storage is tied to the release or container filesystem.",[11,44031,44033],{"id":44032},"_5-configure-nginx-to-serve-static-and-media-separately","5. Configure Nginx to serve static and media separately",[16,44035,44036],{},"In production, the reverse proxy should usually serve both file types directly.",[16,44038,44039],{},"This example shows only file and proxy routing. In production, serve the site over HTTPS, either in this Nginx config or with TLS terminated by a load balancer or CDN.",[106,44041,44043],{"className":2154,"code":44042,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n        expires 1h;\n        add_header Cache-Control \"public\";\n        access_log off;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n        expires 1h;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp.sock;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,44044,44045,44051,44059,44065,44069,44077,44084,44093,44103,44111,44115,44119,44127,44134,44142,44152,44156,44160,44168,44175,44181,44187,44193,44199,44203],{"__ignoreMap":111},[115,44046,44047,44049],{"class":117,"line":118},[115,44048,2163],{"class":121},[115,44050,2166],{"class":125},[115,44052,44053,44055,44057],{"class":117,"line":136},[115,44054,2171],{"class":121},[115,44056,3808],{"class":202},[115,44058,3811],{"class":125},[115,44060,44061,44063],{"class":117,"line":149},[115,44062,2182],{"class":121},[115,44064,2185],{"class":125},[115,44066,44067],{"class":117,"line":162},[115,44068,310],{"emptyLinePlaceholder":309},[115,44070,44071,44073,44075],{"class":117,"line":175},[115,44072,2214],{"class":121},[115,44074,2217],{"class":262},[115,44076,2220],{"class":125},[115,44078,44079,44081],{"class":117,"line":350},[115,44080,2225],{"class":121},[115,44082,44083],{"class":125},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fstatic\u002F;\n",[115,44085,44086,44088,44091],{"class":117,"line":365},[115,44087,42584],{"class":121},[115,44089,44090],{"class":202},"1h",[115,44092,3811],{"class":125},[115,44094,44095,44097,44099,44101],{"class":117,"line":380},[115,44096,42594],{"class":121},[115,44098,42597],{"class":125},[115,44100,42600],{"class":132},[115,44102,3811],{"class":125},[115,44104,44105,44107,44109],{"class":117,"line":487},[115,44106,42575],{"class":121},[115,44108,7103],{"class":202},[115,44110,3811],{"class":125},[115,44112,44113],{"class":117,"line":2095},[115,44114,2233],{"class":125},[115,44116,44117],{"class":117,"line":2104},[115,44118,310],{"emptyLinePlaceholder":309},[115,44120,44121,44123,44125],{"class":117,"line":2113},[115,44122,2214],{"class":121},[115,44124,2244],{"class":262},[115,44126,2220],{"class":125},[115,44128,44129,44131],{"class":117,"line":2122},[115,44130,2225],{"class":121},[115,44132,44133],{"class":125},"\u002Fvar\u002Fwww\u002Fmyapp\u002Fshared\u002Fmedia\u002F;\n",[115,44135,44136,44138,44140],{"class":117,"line":2131},[115,44137,42584],{"class":121},[115,44139,44090],{"class":202},[115,44141,3811],{"class":125},[115,44143,44144,44146,44148,44150],{"class":117,"line":2136},[115,44145,42594],{"class":121},[115,44147,42597],{"class":125},[115,44149,42600],{"class":132},[115,44151,3811],{"class":125},[115,44153,44154],{"class":117,"line":2142},[115,44155,2233],{"class":125},[115,44157,44158],{"class":117,"line":2273},[115,44159,310],{"emptyLinePlaceholder":309},[115,44161,44162,44164,44166],{"class":117,"line":2282},[115,44163,2214],{"class":121},[115,44165,2268],{"class":262},[115,44167,2220],{"class":125},[115,44169,44170,44172],{"class":117,"line":2291},[115,44171,2276],{"class":121},[115,44173,44174],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myapp.sock;\n",[115,44176,44177,44179],{"class":117,"line":2299},[115,44178,2285],{"class":121},[115,44180,2288],{"class":125},[115,44182,44183,44185],{"class":117,"line":2307},[115,44184,2285],{"class":121},[115,44186,3767],{"class":125},[115,44188,44189,44191],{"class":117,"line":2315},[115,44190,2285],{"class":121},[115,44192,2312],{"class":125},[115,44194,44195,44197],{"class":117,"line":2320},[115,44196,2285],{"class":121},[115,44198,2304],{"class":125},[115,44200,44201],{"class":117,"line":7083},[115,44202,2233],{"class":125},[115,44204,44205],{"class":117,"line":7090},[115,44206,2323],{"class":125},[16,44208,44209],{},"Important details:",[63,44211,44212,44221,44224,44232],{},[66,44213,44214,44216,44217,44220],{},[20,44215,13001],{}," path should end with ",[20,44218,44219],{},"\u002F"," when mapping directory locations",[66,44222,44223],{},"static and media locations must point to different directories",[66,44225,44226,44227,4493,44229,44231],{},"do not accidentally proxy ",[20,44228,11729],{},[20,44230,13085],{}," to Gunicorn unless you intentionally need protected media",[66,44233,44234,44235,44238],{},"use ",[20,44236,44237],{},"immutable"," cache headers only when your static filenames are versioned or hashed, such as with Django manifest-based static file storage",[16,44240,44241],{},"Test config and reload:",[106,44243,44244],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,44245,44246,44254],{"__ignoreMap":111},[115,44247,44248,44250,44252],{"class":117,"line":118},[115,44249,2001],{"class":262},[115,44251,3906],{"class":132},[115,44253,4282],{"class":202},[115,44255,44256,44258,44260,44262],{"class":117,"line":136},[115,44257,2001],{"class":262},[115,44259,3480],{"class":132},[115,44261,3919],{"class":132},[115,44263,1996],{"class":132},[16,44265,8572],{},[106,44267,44269],{"className":108,"code":44268,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\ncurl -I https:\u002F\u002Fexample.com\u002Fmedia\u002F\u003Cactual-upload-path>\n",[20,44270,44271,44279],{"__ignoreMap":111},[115,44272,44273,44275,44277],{"class":117,"line":118},[115,44274,2764],{"class":262},[115,44276,2767],{"class":202},[115,44278,13405],{"class":132},[115,44280,44281,44283,44285,44287,44289,44291,44293],{"class":117,"line":136},[115,44282,2764],{"class":262},[115,44284,2767],{"class":202},[115,44286,44016],{"class":132},[115,44288,22810],{"class":121},[115,44290,44021],{"class":132},[115,44292,44024],{"class":125},[115,44294,17380],{"class":121},[16,44296,44297],{},"If TLS is terminated upstream, make sure your Django production settings also trust the proxy correctly for secure requests and redirects.",[11,44299,44301],{"id":44300},"_6-handle-containers-differently-from-traditional-servers","6. Handle containers differently from traditional servers",[16,44303,44304],{},"For Docker or other container deployments:",[63,44306,44307,44310,44313],{},[66,44308,44309],{},"static files can be built into the image or collected in a release step",[66,44311,44312],{},"media files must go to a mounted volume or external object storage",[66,44314,44315],{},"do not rely on container-local storage for uploads",[16,44317,44318],{},"A simple Compose pattern:",[106,44320,44322],{"className":2485,"code":44321,"language":2487,"meta":111,"style":111},"services:\n  app:\n    image: myapp:latest\n    environment:\n      DJANGO_STATIC_ROOT: \u002Fapp\u002Fstatic_collected\n      DJANGO_MEDIA_ROOT: \u002Fapp\u002Fmedia\n    volumes:\n      - media_data:\u002Fapp\u002Fmedia\n\nvolumes:\n  media_data:\n",[20,44323,44324,44330,44337,44345,44351,44361,44371,44377,44383,44387,44393],{"__ignoreMap":111},[115,44325,44326,44328],{"class":117,"line":118},[115,44327,2495],{"class":2494},[115,44329,2498],{"class":125},[115,44331,44332,44335],{"class":117,"line":136},[115,44333,44334],{"class":2494},"  app",[115,44336,2498],{"class":125},[115,44338,44339,44341,44343],{"class":117,"line":149},[115,44340,2510],{"class":2494},[115,44342,2513],{"class":125},[115,44344,5946],{"class":132},[115,44346,44347,44349],{"class":117,"line":162},[115,44348,27844],{"class":2494},[115,44350,2498],{"class":125},[115,44352,44353,44356,44358],{"class":117,"line":175},[115,44354,44355],{"class":2494},"      DJANGO_STATIC_ROOT",[115,44357,2513],{"class":125},[115,44359,44360],{"class":132},"\u002Fapp\u002Fstatic_collected\n",[115,44362,44363,44366,44368],{"class":117,"line":350},[115,44364,44365],{"class":2494},"      DJANGO_MEDIA_ROOT",[115,44367,2513],{"class":125},[115,44369,44370],{"class":132},"\u002Fapp\u002Fmedia\n",[115,44372,44373,44375],{"class":117,"line":365},[115,44374,9733],{"class":2494},[115,44376,2498],{"class":125},[115,44378,44379,44381],{"class":117,"line":380},[115,44380,5976],{"class":125},[115,44382,25798],{"class":132},[115,44384,44385],{"class":117,"line":487},[115,44386,310],{"emptyLinePlaceholder":309},[115,44388,44389,44391],{"class":117,"line":2095},[115,44390,25976],{"class":2494},[115,44392,2498],{"class":125},[115,44394,44395,44397],{"class":117,"line":2104},[115,44396,25990],{"class":2494},[115,44398,2498],{"class":125},[16,44400,44401,44402,44404],{},"If you run ",[20,44403,13689],{}," inside the container, make sure your reverse proxy serves the collected static path from the image or from a mounted or shared location. Do not assume container-local static output is automatically available to Nginx.",[16,44406,44407],{},"Practical container patterns for static files usually look like one of these:",[63,44409,44410,44413,44416],{},[66,44411,44412],{},"collect static during image build and let the proxy serve that path from the container image or a copied artifact",[66,44414,44415],{},"collect static in a dedicated release job and publish it to shared storage or object storage",[66,44417,44418],{},"mount a shared static volume only if that fits your deployment model",[16,44420,44421],{},"For multi-instance deployments, media should use shared storage or object storage. A single local Docker volume is only suitable for a single-host deployment.",[16,44423,44424],{},"Verification after container restart:",[63,44426,44427,44430,44433],{},[66,44428,44429],{},"upload a file",[66,44431,44432],{},"restart the container",[66,44434,44435],{},"confirm the file is still available",[11,44437,44439],{"id":44438},"_7-secure-media-more-carefully-than-static","7. Secure media more carefully than static",[16,44441,44442],{},"Static files are trusted application assets. Media files are untrusted user input.",[16,44444,44445],{},"Production media precautions:",[63,44447,44448,44451,44454,44457],{},[66,44449,44450],{},"store uploads outside the application code directory",[66,44452,44453],{},"validate file type and size in application logic",[66,44455,44456],{},"do not allow uploaded files to be executed as code",[66,44458,44459],{},"consider private media patterns for invoices, reports, or user-specific documents",[16,44461,44462],{},"Static file precautions:",[63,44464,44465,44468,44471],{},[66,44466,44467],{},"keep static read-only after deployment where possible",[66,44469,44470],{},"use long cache headers only for versioned assets",[66,44472,44473],{},"do not allow runtime writes to static directories",[16,44475,44476],{},"A common mistake is serving uploaded content from a path where executable behavior could be enabled by another service. Keep the upload path simple and isolated.",[11,44478,44480],{"id":44479},"_8-add-backups-and-recovery-for-media","8. Add backups and recovery for media",[16,44482,44483],{},"You can regenerate static files from source. You usually cannot regenerate user uploads.",[16,44485,44486],{},"Your backup plan should include:",[63,44488,44489,44492,44495,44498],{},[66,44490,44491],{},"media directory or media bucket",[66,44493,44494],{},"retention policy",[66,44496,44497],{},"restore procedure",[66,44499,44500],{},"test restore, not just backup creation",[16,44502,44503,44504,44506,44507,44509,44510,44512],{},"A restore runbook matters more for media than for static. If you lose ",[20,44505,11918],{},", rerun ",[20,44508,13689],{},". If you lose ",[20,44511,15214],{},", you may lose customer data.",[11,44514,1321],{"id":1320},[16,44516,44517,44518,44521],{},"The key difference between ",[1226,44519,44520],{},"Django static files vs media files"," is not just file type. It is operational ownership.",[63,44523,44524,44530],{},[66,44525,44526,44529],{},[1226,44527,44528],{},"Static"," belongs to the application release.",[66,44531,44532,44535],{},[1226,44533,44534],{},"Media"," belongs to the running system and its users.",[16,44537,44538],{},"That distinction drives the right production architecture:",[63,44540,44541,44544,44547,44550,44553],{},[66,44542,44543],{},"separate paths",[66,44545,44546],{},"separate permissions",[66,44548,44549],{},"separate caching rules",[66,44551,44552],{},"separate backup policy",[66,44554,44555],{},"separate scaling strategy",[16,44557,44558],{},"For single-server deployments, local filesystem storage is often enough if media is outside the release directory and included in backups. For multi-server deployments, local media usually stops working because uploads land on only one node. In that case, use shared storage or object storage for media, while static can still be distributed through the reverse proxy, image build, shared storage, or a CDN.",[16,44560,44561,44562,44564],{},"If your deployment process keeps repeating the same checks, this is a good place to standardize. The first things worth automating are path validation, ",[20,44563,13689],{},", proxy config generation, and post-deploy checks for known static and media URLs. That reduces drift without changing the underlying architecture.",[11,44566,1337],{"id":1336},[63,44568,44569,44579,44592,44598,44607,44616,44622],{},[66,44570,44571,44578],{},[1226,44572,44573,44574,3146,44576,211],{},"Do not use the same directory for ",[20,44575,11918],{},[20,44577,15214],{}," This creates collisions, bad cleanup behavior, and security confusion.",[66,44580,44581,44584,44585,4493,44588,44591],{},[1226,44582,44583],{},"Do not depend on Django development helpers in production."," URL patterns added for ",[20,44586,44587],{},"static()",[20,44589,44590],{},"media()"," serving are for development convenience.",[66,44593,44594,44597],{},[1226,44595,44596],{},"Be careful with release cleanup scripts."," If media lives under a release directory, a normal cleanup can delete uploads.",[66,44599,44600,44603,44604,44606],{},[1226,44601,44602],{},"Watch proxy path mismatches."," An incorrect Nginx ",[20,44605,13001],{}," is a common reason static or media returns 404.",[66,44608,44609,44615],{},[1226,44610,44611,44612,44614],{},"For private files, do not expose ",[20,44613,13085],{}," publicly by default."," Route access through application authorization or a protected file-serving pattern.",[66,44617,44618,44621],{},[1226,44619,44620],{},"If using hashed static assets,"," app version and collected static files must stay aligned during rollback.",[66,44623,44624,44627,44628,1153,44630,44632],{},[1226,44625,44626],{},"This page covers file handling only."," Production deployments also need correct Django security and proxy settings such as ",[20,44629,32431],{},[20,44631,2719],{},", CSRF configuration, and secure proxy handling.",[11,44634,1386],{"id":1385},[16,44636,44637,44638,211],{},"For broader production configuration, see ",[1395,44639,3007],{"href":3006},[16,44641,44642,44643,211],{},"If you need the full reverse proxy and app server setup, continue with ",[1395,44644,4425],{"href":2985},[16,44646,44647,44648,211],{},"For a deployment checklist around release safety, see ",[1395,44649,3000],{"href":2999},[16,44651,44652,44653,211],{},"If you are deciding what server interface to run, see ",[1395,44654,3014],{"href":3013},[11,44656,1420],{"id":1419},[52,44658,11553,44660,3146,44662,44664],{"id":44659},"what-is-the-difference-between-static_root-and-media_root-in-django",[20,44661,11918],{},[20,44663,15214],{}," in Django?",[16,44666,44667,44669,44670,44672,44673,44675],{},[20,44668,11918],{}," is the destination directory for collected application assets after running ",[20,44671,13689],{},". ",[20,44674,15214],{}," is the runtime directory where uploaded files are stored. Static can be rebuilt; media must be preserved.",[52,44677,44679],{"id":44678},"should-django-serve-static-and-media-files-in-production","Should Django serve static and media files in production?",[16,44681,44682],{},"Usually no. In production, Nginx, Caddy, object storage, or a CDN should serve them. Django should handle application logic, not routine file delivery.",[52,44684,44686],{"id":44685},"can-i-store-static-files-and-user-uploads-in-the-same-directory","Can I store static files and user uploads in the same directory?",[16,44688,44689],{},"No. That is a common production mistake. It creates permission problems, deploy risks, cache issues, and a higher chance of deleting uploads during releases.",[52,44691,44693],{"id":44692},"what-happens-to-media-files-when-i-redeploy-a-django-app","What happens to media files when I redeploy a Django app?",[16,44695,44696],{},"They should remain unchanged if media is stored in a persistent location outside the release directory or in external storage. If media is inside the app release path or container filesystem, redeploys may delete it.",[52,44698,44700],{"id":44699},"how-should-i-handle-media-files-on-docker-or-multi-server-deployments","How should I handle media files on Docker or multi-server deployments?",[16,44702,44703],{},"Use a mounted persistent volume for single-host containers. For multi-server or autoscaled deployments, use shared storage or object storage so every app instance can access the same uploaded files.",[1485,44705,44706],{},"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":44708},[44709,44710,44711,44712,44713,44714,44716,44717,44718,44719,44720,44721,44722,44723,44724],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":43629,"depth":136,"text":43630},{"id":43780,"depth":136,"text":43781},{"id":43841,"depth":136,"text":44715},"3. Run collectstatic during deploy, not ad hoc",{"id":43937,"depth":136,"text":43938},{"id":44032,"depth":136,"text":44033},{"id":44300,"depth":136,"text":44301},{"id":44438,"depth":136,"text":44439},{"id":44479,"depth":136,"text":44480},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":44725},[44726,44728,44729,44730,44731],{"id":44659,"depth":149,"text":44727},"What is the difference between STATIC_ROOT and MEDIA_ROOT in Django?",{"id":44678,"depth":149,"text":44679},{"id":44685,"depth":149,"text":44686},{"id":44692,"depth":149,"text":44693},{"id":44699,"depth":149,"text":44700},"Many Django deployments break because static files and media files are treated as the same thing.",{},"\u002Fdjango-static-vs-media-files-production",[43448,3013,44736],"\u002Fdeploy\u002Fbest-hosting-options-for-django-deployment",{"title":34041,"description":44732},[1557,43557],"django-static-vs-media-files-production",[1557,43557],"9NXv1KJtT7lISZj8yQiRg0ogoOdsE4H84fTBNC6vyRA",{"id":44743,"title":3014,"body":44744,"category":3088,"description":46532,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":46533,"navigation":309,"path":46534,"priority":3351,"related":46535,"role":3097,"section":3098,"seo":46536,"stack":46537,"stem":46538,"tags":46539,"type":3104,"__hash__":46540},"articles\u002Fdjango-wsgi-vs-asgi.md",{"type":8,"value":44745,"toc":46494},[44746,44748,44751,44754,44762,44765,44784,44786,44792,44798,44815,44818,44839,44844,44846,44850,44853,44903,44908,44912,44915,44918,44971,45019,45022,45034,45038,45042,45058,45061,45065,45088,45092,45112,45115,45119,45122,45126,45239,45243,45356,45359,45362,45365,45460,45470,45473,45497,45500,45533,45536,45569,45573,45576,45580,45678,45681,45815,45818,45863,45869,45886,45889,45911,45915,45918,45955,45958,45984,45988,45991,46004,46007,46020,46022,46040,46042,46061,46064,46095,46099,46102,46105,46130,46133,46190,46193,46195,46199,46202,46205,46223,46226,46230,46233,46248,46250,46264,46267,46271,46274,46277,46294,46297,46318,46320,46326,46328,46385,46387,46393,46396,46408,46411,46421,46427,46429,46433,46442,46446,46449,46453,46456,46460,46463,46467,46470,46488,46491],[11,44747,14],{"id":13},[16,44749,44750],{},"When you deploy Django in production, you will see both WSGI and ASGI recommended. The confusing part is that both are valid, both are supported by Django, and both can sit behind the same reverse proxy.",[16,44752,44753],{},"The real deployment question is not “which one is newer?” It is:",[63,44755,44756,44759],{},[66,44757,44758],{},"do you need a simple and stable request\u002Fresponse stack, or",[66,44760,44761],{},"do you need async features like websockets, long-lived connections, or an async-first service model?",[16,44763,44764],{},"Your choice affects:",[63,44766,44767,44770,44775,44778,44781],{},[66,44768,44769],{},"which app server you run",[66,44771,44772,44773],{},"how you configure process supervision with ",[20,44774,1277],{},[66,44776,44777],{},"what Nginx must proxy",[66,44779,44780],{},"what failure modes you need to monitor",[66,44782,44783],{},"how hard rollback will be if you switch later",[11,44785,30],{"id":29},[16,44787,44788,44789,28825],{},"For most traditional Django apps, deploy ",[1226,44790,44791],{},"WSGI",[16,44793,55,44794,44797],{},[1226,44795,44796],{},"ASGI"," when you clearly need:",[63,44799,44800,44803,44806,44809,44812],{},[66,44801,44802],{},"websockets",[66,44804,44805],{},"long-lived connections",[66,44807,44808],{},"Django Channels",[66,44810,44811],{},"streaming or realtime features",[66,44813,44814],{},"async views backed by truly async dependencies",[16,44816,44817],{},"A practical default is:",[63,44819,44820,44828],{},[66,44821,44822,2957,44825],{},[1226,44823,44824],{},"WSGI stack:",[20,44826,44827],{},"Nginx -> Gunicorn -> Django wsgi.py",[66,44829,44830,2957,44833,4493,44836],{},[1226,44831,44832],{},"ASGI stack:",[20,44834,44835],{},"Nginx -> Uvicorn",[20,44837,44838],{},"Nginx -> Gunicorn with Uvicorn workers -> Django asgi.py",[16,44840,32264,44841,44843],{},[1226,44842,7474],{}," change: you still need TLS, secrets management, static files, migrations, health checks, logging, and rollback steps.",[11,44845,43],{"id":42},[52,44847,44849],{"id":44848},"_1-identify-what-your-app-actually-needs","1. Identify what your app actually needs",[16,44851,44852],{},"Use this decision rule before changing anything in production:",[63,44854,44855,44878],{},[66,44856,44857,44860,44861],{},[1226,44858,44859],{},"Choose WSGI"," if your app is mostly:",[63,44862,44863,44866,44869,44872,44875],{},[66,44864,44865],{},"admin",[66,44867,44868],{},"CRUD",[66,44870,44871],{},"normal HTML pages",[66,44873,44874],{},"standard REST APIs",[66,44876,44877],{},"background tasks handled separately by Celery or cron",[66,44879,44880,44883,44884],{},[1226,44881,44882],{},"Choose ASGI"," if your app needs:",[63,44885,44886,44888,44891,44894,44897,44900],{},[66,44887,44802],{},[66,44889,44890],{},"chat or live notifications",[66,44892,44893],{},"collaborative editing",[66,44895,44896],{},"long polling",[66,44898,44899],{},"streaming responses",[66,44901,44902],{},"async endpoints that depend on async libraries",[16,44904,44905,44906,211],{},"If you are unsure and reliability matters more than future flexibility, start with ",[1226,44907,44791],{},[52,44909,44911],{"id":44910},"_2-confirm-the-django-entrypoint-you-will-deploy","2. Confirm the Django entrypoint you will deploy",[16,44913,44914],{},"Django ships with both entrypoints in most projects.",[16,44916,44917],{},"Typical files:",[106,44919,44921],{"className":2369,"code":44920,"language":1114,"meta":111,"style":111},"# project\u002Fwsgi.py\nimport os\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"project.settings\")\napplication = get_wsgi_application()\n",[20,44922,44923,44928,44934,44946,44950,44962],{"__ignoreMap":111},[115,44924,44925],{"class":117,"line":118},[115,44926,44927],{"class":3861},"# project\u002Fwsgi.py\n",[115,44929,44930,44932],{"class":117,"line":136},[115,44931,5613],{"class":121},[115,44933,5616],{"class":125},[115,44935,44936,44938,44941,44943],{"class":117,"line":149},[115,44937,5621],{"class":121},[115,44939,44940],{"class":125}," django.core.wsgi ",[115,44942,5613],{"class":121},[115,44944,44945],{"class":125}," get_wsgi_application\n",[115,44947,44948],{"class":117,"line":162},[115,44949,310],{"emptyLinePlaceholder":309},[115,44951,44952,44954,44956,44958,44960],{"class":117,"line":175},[115,44953,5638],{"class":125},[115,44955,5641],{"class":132},[115,44957,1153],{"class":125},[115,44959,11799],{"class":132},[115,44961,2394],{"class":125},[115,44963,44964,44966,44968],{"class":117,"line":350},[115,44965,11806],{"class":125},[115,44967,129],{"class":121},[115,44969,44970],{"class":125}," get_wsgi_application()\n",[106,44972,44973],{"className":2369,"code":11759,"language":1114,"meta":111,"style":111},[20,44974,44975,44979,44985,44995,44999,45011],{"__ignoreMap":111},[115,44976,44977],{"class":117,"line":118},[115,44978,11766],{"class":3861},[115,44980,44981,44983],{"class":117,"line":136},[115,44982,5613],{"class":121},[115,44984,5616],{"class":125},[115,44986,44987,44989,44991,44993],{"class":117,"line":149},[115,44988,5621],{"class":121},[115,44990,11779],{"class":125},[115,44992,5613],{"class":121},[115,44994,11784],{"class":125},[115,44996,44997],{"class":117,"line":162},[115,44998,310],{"emptyLinePlaceholder":309},[115,45000,45001,45003,45005,45007,45009],{"class":117,"line":175},[115,45002,5638],{"class":125},[115,45004,5641],{"class":132},[115,45006,1153],{"class":125},[115,45008,11799],{"class":132},[115,45010,2394],{"class":125},[115,45012,45013,45015,45017],{"class":117,"line":350},[115,45014,11806],{"class":125},[115,45016,129],{"class":121},[115,45018,11811],{"class":125},[16,45020,45021],{},"Your app server loads one of these:",[63,45023,45024,45029],{},[66,45025,45026,45027],{},"WSGI server loads ",[20,45028,16986],{},[66,45030,45031,45032],{},"ASGI server loads ",[20,45033,11669],{},[52,45035,45037],{"id":45036},"_3-run-the-correct-app-server-command","3. Run the correct app server command",[1850,45039,45041],{"id":45040},"wsgi-with-gunicorn","WSGI with Gunicorn",[106,45043,45045],{"className":108,"code":45044,"language":110,"meta":111,"style":111},"gunicorn --bind 127.0.0.1:8000 project.wsgi:application\n",[20,45046,45047],{"__ignoreMap":111},[115,45048,45049,45051,45053,45055],{"class":117,"line":118},[115,45050,14954],{"class":262},[115,45052,23605],{"class":202},[115,45054,23608],{"class":132},[115,45056,45057],{"class":132}," project.wsgi:application\n",[16,45059,45060],{},"This is the common production default for standard Django deployments.",[1850,45062,45064],{"id":45063},"asgi-with-uvicorn","ASGI with Uvicorn",[106,45066,45068],{"className":108,"code":45067,"language":110,"meta":111,"style":111},"uvicorn project.asgi:application --host 127.0.0.1 --port 8000\n",[20,45069,45070],{"__ignoreMap":111},[115,45071,45072,45074,45077,45080,45083,45086],{"class":117,"line":118},[115,45073,12359],{"class":262},[115,45075,45076],{"class":132}," project.asgi:application",[115,45078,45079],{"class":202}," --host",[115,45081,45082],{"class":202}," 127.0.0.1",[115,45084,45085],{"class":202}," --port",[115,45087,23864],{"class":202},[1850,45089,45091],{"id":45090},"asgi-with-gunicorn-and-uvicorn-workers","ASGI with Gunicorn and Uvicorn workers",[106,45093,45095],{"className":108,"code":45094,"language":110,"meta":111,"style":111},"gunicorn project.asgi:application -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:8000\n",[20,45096,45097],{"__ignoreMap":111},[115,45098,45099,45101,45103,45105,45108,45110],{"class":117,"line":118},[115,45100,14954],{"class":262},[115,45102,45076],{"class":132},[115,45104,3936],{"class":202},[115,45106,45107],{"class":132}," uvicorn.workers.UvicornWorker",[115,45109,23605],{"class":202},[115,45111,32827],{"class":132},[16,45113,45114],{},"This hybrid setup is common when teams want Gunicorn-style process management but need an ASGI app.",[52,45116,45118],{"id":45117},"_4-put-process-management-under-systemd","4. Put process management under systemd",[16,45120,45121],{},"Do not run these commands manually in production.",[1850,45123,45125],{"id":45124},"example-wsgi-service","Example WSGI service",[106,45127,45129],{"className":2026,"code":45128,"language":2028,"meta":111,"style":111},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\n[Unit]\nDescription=Gunicorn for Django project\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fproject\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fproject\u002Fproject.env\nExecStart=\u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Fgunicorn \\\n    --workers 3 \\\n    --bind 127.0.0.1:8000 \\\n    project.wsgi:application\nRestart=always\nRestartSec=5\nTimeoutStopSec=30\n\n[Install]\nWantedBy=multi-user.target\n",[20,45130,45131,45136,45140,45147,45153,45157,45161,45167,45173,45180,45186,45193,45197,45201,45206,45212,45218,45225,45229,45233],{"__ignoreMap":111},[115,45132,45133],{"class":117,"line":118},[115,45134,45135],{"class":3861},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\n",[115,45137,45138],{"class":117,"line":136},[115,45139,2035],{"class":262},[115,45141,45142,45144],{"class":117,"line":149},[115,45143,2040],{"class":121},[115,45145,45146],{"class":125},"=Gunicorn for Django project\n",[115,45148,45149,45151],{"class":117,"line":162},[115,45150,2048],{"class":121},[115,45152,2051],{"class":125},[115,45154,45155],{"class":117,"line":175},[115,45156,310],{"emptyLinePlaceholder":309},[115,45158,45159],{"class":117,"line":350},[115,45160,2060],{"class":262},[115,45162,45163,45165],{"class":117,"line":365},[115,45164,2065],{"class":121},[115,45166,2076],{"class":125},[115,45168,45169,45171],{"class":117,"line":380},[115,45170,2073],{"class":121},[115,45172,2076],{"class":125},[115,45174,45175,45177],{"class":117,"line":487},[115,45176,2081],{"class":121},[115,45178,45179],{"class":125},"=\u002Fsrv\u002Fproject\u002Fcurrent\n",[115,45181,45182,45184],{"class":117,"line":2095},[115,45183,2089],{"class":121},[115,45185,12568],{"class":125},[115,45187,45188,45190],{"class":117,"line":2104},[115,45189,2107],{"class":121},[115,45191,45192],{"class":125},"=\u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Fgunicorn \\\n",[115,45194,45195],{"class":117,"line":2113},[115,45196,15417],{"class":125},[115,45198,45199],{"class":117,"line":2122},[115,45200,23722],{"class":125},[115,45202,45203],{"class":117,"line":2131},[115,45204,45205],{"class":125},"    project.wsgi:application\n",[115,45207,45208,45210],{"class":117,"line":2136},[115,45209,2116],{"class":121},[115,45211,4932],{"class":125},[115,45213,45214,45216],{"class":117,"line":2142},[115,45215,2125],{"class":121},[115,45217,2128],{"class":125},[115,45219,45220,45222],{"class":117,"line":2273},[115,45221,9286],{"class":121},[115,45223,45224],{"class":125},"=30\n",[115,45226,45227],{"class":117,"line":2282},[115,45228,310],{"emptyLinePlaceholder":309},[115,45230,45231],{"class":117,"line":2291},[115,45232,2139],{"class":262},[115,45234,45235,45237],{"class":117,"line":2299},[115,45236,2145],{"class":121},[115,45238,2148],{"class":125},[1850,45240,45242],{"id":45241},"example-asgi-service-with-uvicorn","Example ASGI service with Uvicorn",[106,45244,45246],{"className":2026,"code":45245,"language":2028,"meta":111,"style":111},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fuvicorn.service\n[Unit]\nDescription=Uvicorn for Django project\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fproject\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fproject\u002Fproject.env\nExecStart=\u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Fuvicorn \\\n    project.asgi:application \\\n    --host 127.0.0.1 \\\n    --port 8000\nRestart=always\nRestartSec=5\nTimeoutStopSec=30\n\n[Install]\nWantedBy=multi-user.target\n",[20,45247,45248,45253,45257,45264,45270,45274,45278,45284,45290,45296,45302,45309,45314,45319,45324,45330,45336,45342,45346,45350],{"__ignoreMap":111},[115,45249,45250],{"class":117,"line":118},[115,45251,45252],{"class":3861},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fuvicorn.service\n",[115,45254,45255],{"class":117,"line":136},[115,45256,2035],{"class":262},[115,45258,45259,45261],{"class":117,"line":149},[115,45260,2040],{"class":121},[115,45262,45263],{"class":125},"=Uvicorn for Django project\n",[115,45265,45266,45268],{"class":117,"line":162},[115,45267,2048],{"class":121},[115,45269,2051],{"class":125},[115,45271,45272],{"class":117,"line":175},[115,45273,310],{"emptyLinePlaceholder":309},[115,45275,45276],{"class":117,"line":350},[115,45277,2060],{"class":262},[115,45279,45280,45282],{"class":117,"line":365},[115,45281,2065],{"class":121},[115,45283,2076],{"class":125},[115,45285,45286,45288],{"class":117,"line":380},[115,45287,2073],{"class":121},[115,45289,2076],{"class":125},[115,45291,45292,45294],{"class":117,"line":487},[115,45293,2081],{"class":121},[115,45295,45179],{"class":125},[115,45297,45298,45300],{"class":117,"line":2095},[115,45299,2089],{"class":121},[115,45301,12568],{"class":125},[115,45303,45304,45306],{"class":117,"line":2104},[115,45305,2107],{"class":121},[115,45307,45308],{"class":125},"=\u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Fuvicorn \\\n",[115,45310,45311],{"class":117,"line":2113},[115,45312,45313],{"class":125},"    project.asgi:application \\\n",[115,45315,45316],{"class":117,"line":2122},[115,45317,45318],{"class":125},"    --host 127.0.0.1 \\\n",[115,45320,45321],{"class":117,"line":2131},[115,45322,45323],{"class":125},"    --port 8000\n",[115,45325,45326,45328],{"class":117,"line":2136},[115,45327,2116],{"class":121},[115,45329,4932],{"class":125},[115,45331,45332,45334],{"class":117,"line":2142},[115,45333,2125],{"class":121},[115,45335,2128],{"class":125},[115,45337,45338,45340],{"class":117,"line":2273},[115,45339,9286],{"class":121},[115,45341,45224],{"class":125},[115,45343,45344],{"class":117,"line":2282},[115,45345,310],{"emptyLinePlaceholder":309},[115,45347,45348],{"class":117,"line":2291},[115,45349,2139],{"class":262},[115,45351,45352,45354],{"class":117,"line":2299},[115,45353,2145],{"class":121},[115,45355,2148],{"class":125},[16,45357,45358],{},"For production, a single Uvicorn process may be enough for small apps, but higher traffic usually needs multiple workers or a Gunicorn + Uvicorn worker setup.",[16,45360,45361],{},"Store secrets in an environment file, not in the service unit. That file should be outside your repository, root-owned, and readable only by the service group your app runs under.",[16,45363,45364],{},"Example using Django-native database variables:",[106,45366,45368],{"className":108,"code":45367,"language":110,"meta":111,"style":111},"# \u002Fetc\u002Fproject\u002Fproject.env\nDJANGO_SETTINGS_MODULE=project.settings\nDJANGO_SECRET_KEY=replace-me\nDJANGO_DEBUG=False\nDJANGO_ALLOWED_HOSTS=example.com\n\nDB_NAME=project\nDB_USER=projectuser\nDB_PASSWORD=replace-me\nDB_HOST=127.0.0.1\nDB_PORT=5432\n",[20,45369,45370,45375,45384,45393,45403,45413,45417,45426,45435,45443,45452],{"__ignoreMap":111},[115,45371,45372],{"class":117,"line":118},[115,45373,45374],{"class":3861},"# \u002Fetc\u002Fproject\u002Fproject.env\n",[115,45376,45377,45379,45381],{"class":117,"line":136},[115,45378,5074],{"class":125},[115,45380,129],{"class":121},[115,45382,45383],{"class":132},"project.settings\n",[115,45385,45386,45388,45390],{"class":117,"line":149},[115,45387,34901],{"class":125},[115,45389,129],{"class":121},[115,45391,45392],{"class":132},"replace-me\n",[115,45394,45395,45398,45400],{"class":117,"line":162},[115,45396,45397],{"class":125},"DJANGO_DEBUG",[115,45399,129],{"class":121},[115,45401,45402],{"class":132},"False\n",[115,45404,45405,45408,45410],{"class":117,"line":175},[115,45406,45407],{"class":125},"DJANGO_ALLOWED_HOSTS",[115,45409,129],{"class":121},[115,45411,45412],{"class":132},"example.com\n",[115,45414,45415],{"class":117,"line":350},[115,45416,310],{"emptyLinePlaceholder":309},[115,45418,45419,45421,45423],{"class":117,"line":365},[115,45420,34936],{"class":125},[115,45422,129],{"class":121},[115,45424,45425],{"class":132},"project\n",[115,45427,45428,45430,45432],{"class":117,"line":380},[115,45429,34944],{"class":125},[115,45431,129],{"class":121},[115,45433,45434],{"class":132},"projectuser\n",[115,45436,45437,45439,45441],{"class":117,"line":487},[115,45438,34952],{"class":125},[115,45440,129],{"class":121},[115,45442,45392],{"class":132},[115,45444,45445,45447,45449],{"class":117,"line":2095},[115,45446,34960],{"class":125},[115,45448,129],{"class":121},[115,45450,45451],{"class":132},"127.0.0.1\n",[115,45453,45454,45456,45458],{"class":117,"line":2104},[115,45455,34968],{"class":125},[115,45457,129],{"class":121},[115,45459,10464],{"class":132},[16,45461,45462,45463,45466,45467,45469],{},"If your settings module parses a DSN with a helper such as ",[20,45464,45465],{},"dj-database-url",", a ",[20,45468,10873],{}," variable is also valid, but Django does not read that automatically by itself.",[16,45471,45472],{},"Protect the env file:",[106,45474,45475],{"className":108,"code":12460,"language":110,"meta":111,"style":111},[20,45476,45477,45487],{"__ignoreMap":111},[115,45478,45479,45481,45483,45485],{"class":117,"line":118},[115,45480,2001],{"class":262},[115,45482,6733],{"class":132},[115,45484,12471],{"class":132},[115,45486,12411],{"class":132},[115,45488,45489,45491,45493,45495],{"class":117,"line":136},[115,45490,2001],{"class":262},[115,45492,12480],{"class":132},[115,45494,12483],{"class":202},[115,45496,12411],{"class":132},[16,45498,45499],{},"Then enable and start the service:",[106,45501,45503],{"className":108,"code":45502,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable gunicorn\nsudo systemctl start gunicorn\n",[20,45504,45505,45513,45523],{"__ignoreMap":111},[115,45506,45507,45509,45511],{"class":117,"line":118},[115,45508,2001],{"class":262},[115,45510,3480],{"class":132},[115,45512,4984],{"class":132},[115,45514,45515,45517,45519,45521],{"class":117,"line":136},[115,45516,2001],{"class":262},[115,45518,3480],{"class":132},[115,45520,8567],{"class":132},[115,45522,1987],{"class":132},[115,45524,45525,45527,45529,45531],{"class":117,"line":149},[115,45526,2001],{"class":262},[115,45528,3480],{"class":132},[115,45530,15489],{"class":132},[115,45532,1987],{"class":132},[16,45534,45535],{},"Or for ASGI:",[106,45537,45539],{"className":108,"code":45538,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable uvicorn\nsudo systemctl start uvicorn\n",[20,45540,45541,45549,45559],{"__ignoreMap":111},[115,45542,45543,45545,45547],{"class":117,"line":118},[115,45544,2001],{"class":262},[115,45546,3480],{"class":132},[115,45548,4984],{"class":132},[115,45550,45551,45553,45555,45557],{"class":117,"line":136},[115,45552,2001],{"class":262},[115,45554,3480],{"class":132},[115,45556,8567],{"class":132},[115,45558,12375],{"class":132},[115,45560,45561,45563,45565,45567],{"class":117,"line":149},[115,45562,2001],{"class":262},[115,45564,3480],{"class":132},[115,45566,15489],{"class":132},[115,45568,12375],{"class":132},[52,45570,45572],{"id":45571},"_5-put-nginx-in-front-of-the-app-server","5. Put Nginx in front of the app server",[16,45574,45575],{},"For both WSGI and ASGI, bind the app server to localhost and expose only Nginx publicly.",[1850,45577,45579],{"id":45578},"basic-nginx-reverse-proxy","Basic Nginx reverse proxy",[106,45581,45583],{"className":2154,"code":45582,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fproject\u002Fcurrent\u002Fstaticfiles\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n",[20,45584,45585,45591,45599,45605,45609,45617,45624,45628,45632,45640,45646,45652,45658,45664,45670,45674],{"__ignoreMap":111},[115,45586,45587,45589],{"class":117,"line":118},[115,45588,2163],{"class":121},[115,45590,2166],{"class":125},[115,45592,45593,45595,45597],{"class":117,"line":136},[115,45594,2171],{"class":121},[115,45596,3808],{"class":202},[115,45598,3811],{"class":125},[115,45600,45601,45603],{"class":117,"line":149},[115,45602,2182],{"class":121},[115,45604,2185],{"class":125},[115,45606,45607],{"class":117,"line":162},[115,45608,310],{"emptyLinePlaceholder":309},[115,45610,45611,45613,45615],{"class":117,"line":175},[115,45612,2214],{"class":121},[115,45614,2217],{"class":262},[115,45616,2220],{"class":125},[115,45618,45619,45621],{"class":117,"line":350},[115,45620,2225],{"class":121},[115,45622,45623],{"class":125},"\u002Fsrv\u002Fproject\u002Fcurrent\u002Fstaticfiles\u002F;\n",[115,45625,45626],{"class":117,"line":365},[115,45627,2233],{"class":125},[115,45629,45630],{"class":117,"line":380},[115,45631,310],{"emptyLinePlaceholder":309},[115,45633,45634,45636,45638],{"class":117,"line":487},[115,45635,2214],{"class":121},[115,45637,2268],{"class":262},[115,45639,2220],{"class":125},[115,45641,45642,45644],{"class":117,"line":2095},[115,45643,2276],{"class":121},[115,45645,3748],{"class":125},[115,45647,45648,45650],{"class":117,"line":2104},[115,45649,2285],{"class":121},[115,45651,2288],{"class":125},[115,45653,45654,45656],{"class":117,"line":2113},[115,45655,2285],{"class":121},[115,45657,2304],{"class":125},[115,45659,45660,45662],{"class":117,"line":2122},[115,45661,2285],{"class":121},[115,45663,2312],{"class":125},[115,45665,45666,45668],{"class":117,"line":2131},[115,45667,2285],{"class":121},[115,45669,3767],{"class":125},[115,45671,45672],{"class":117,"line":2136},[115,45673,2233],{"class":125},[115,45675,45676],{"class":117,"line":2142},[115,45677,2323],{"class":125},[16,45679,45680],{},"For ASGI apps using websockets, use a safer upgrade configuration:",[106,45682,45684],{"className":2154,"code":45683,"language":2156,"meta":111,"style":111},"map $http_upgrade $connection_upgrade {\n    default upgrade;\n    ''      close;\n}\n\nserver {\n    listen 80;\n    server_name example.com;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n",[20,45685,45686,45700,45708,45716,45720,45724,45730,45738,45744,45748,45756,45762,45770,45776,45783,45789,45795,45801,45807,45811],{"__ignoreMap":111},[115,45687,45688,45691,45694,45697],{"class":117,"line":118},[115,45689,45690],{"class":121},"map",[115,45692,45693],{"class":125}," $",[115,45695,45696],{"class":5680},"http_upgrade",[115,45698,45699],{"class":125}," $connection_upgrade {\n",[115,45701,45702,45705],{"class":117,"line":136},[115,45703,45704],{"class":202},"    default",[115,45706,45707],{"class":125}," upgrade;\n",[115,45709,45710,45713],{"class":117,"line":149},[115,45711,45712],{"class":132},"    ''",[115,45714,45715],{"class":125},"      close;\n",[115,45717,45718],{"class":117,"line":162},[115,45719,2323],{"class":125},[115,45721,45722],{"class":117,"line":175},[115,45723,310],{"emptyLinePlaceholder":309},[115,45725,45726,45728],{"class":117,"line":350},[115,45727,2163],{"class":121},[115,45729,2166],{"class":125},[115,45731,45732,45734,45736],{"class":117,"line":365},[115,45733,2171],{"class":121},[115,45735,3808],{"class":202},[115,45737,3811],{"class":125},[115,45739,45740,45742],{"class":117,"line":380},[115,45741,2182],{"class":121},[115,45743,2185],{"class":125},[115,45745,45746],{"class":117,"line":487},[115,45747,310],{"emptyLinePlaceholder":309},[115,45749,45750,45752,45754],{"class":117,"line":2095},[115,45751,2214],{"class":121},[115,45753,2268],{"class":262},[115,45755,2220],{"class":125},[115,45757,45758,45760],{"class":117,"line":2104},[115,45759,2276],{"class":121},[115,45761,3748],{"class":125},[115,45763,45764,45766,45768],{"class":117,"line":2113},[115,45765,12876],{"class":121},[115,45767,12879],{"class":202},[115,45769,3811],{"class":125},[115,45771,45772,45774],{"class":117,"line":2122},[115,45773,2285],{"class":121},[115,45775,13775],{"class":125},[115,45777,45778,45780],{"class":117,"line":2131},[115,45779,2285],{"class":121},[115,45781,45782],{"class":125},"Connection $connection_upgrade;\n",[115,45784,45785,45787],{"class":117,"line":2136},[115,45786,2285],{"class":121},[115,45788,2288],{"class":125},[115,45790,45791,45793],{"class":117,"line":2142},[115,45792,2285],{"class":121},[115,45794,2304],{"class":125},[115,45796,45797,45799],{"class":117,"line":2273},[115,45798,2285],{"class":121},[115,45800,2312],{"class":125},[115,45802,45803,45805],{"class":117,"line":2282},[115,45804,2285],{"class":121},[115,45806,3767],{"class":125},[115,45808,45809],{"class":117,"line":2291},[115,45810,2233],{"class":125},[115,45812,45813],{"class":117,"line":2299},[115,45814,2323],{"class":125},[16,45816,45817],{},"If you terminate TLS at Nginx, Django also needs the matching proxy-aware settings:",[106,45819,45821],{"className":2369,"code":45820,"language":1114,"meta":111,"style":111},"# settings.py\nDEBUG = False\nALLOWED_HOSTS = [\"example.com\"]\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n",[20,45822,45823,45827,45835,45847],{"__ignoreMap":111},[115,45824,45825],{"class":117,"line":118},[115,45826,42173],{"class":3861},[115,45828,45829,45831,45833],{"class":117,"line":136},[115,45830,7350],{"class":202},[115,45832,2380],{"class":121},[115,45834,7355],{"class":202},[115,45836,45837,45839,45841,45843,45845],{"class":117,"line":149},[115,45838,2719],{"class":202},[115,45840,2380],{"class":121},[115,45842,7493],{"class":125},[115,45844,7496],{"class":132},[115,45846,2552],{"class":125},[115,45848,45849,45851,45853,45855,45857,45859,45861],{"class":117,"line":162},[115,45850,2377],{"class":202},[115,45852,2380],{"class":121},[115,45854,2383],{"class":125},[115,45856,2386],{"class":132},[115,45858,1153],{"class":125},[115,45860,2391],{"class":132},[115,45862,2394],{"class":125},[16,45864,45865,45866,45868],{},"If you accept cross-origin HTTPS POSTs or use trusted external admin origins, set ",[20,45867,2725],{}," explicitly as well:",[106,45870,45872],{"className":2369,"code":45871,"language":1114,"meta":111,"style":111},"CSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\"]\n",[20,45873,45874],{"__ignoreMap":111},[115,45875,45876,45878,45880,45882,45884],{"class":117,"line":118},[115,45877,2725],{"class":202},[115,45879,2380],{"class":121},[115,45881,7493],{"class":125},[115,45883,15110],{"class":132},[115,45885,2552],{"class":125},[16,45887,45888],{},"Test and reload Nginx:",[106,45890,45891],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,45892,45893,45901],{"__ignoreMap":111},[115,45894,45895,45897,45899],{"class":117,"line":118},[115,45896,2001],{"class":262},[115,45898,3906],{"class":132},[115,45900,4282],{"class":202},[115,45902,45903,45905,45907,45909],{"class":117,"line":136},[115,45904,2001],{"class":262},[115,45906,3480],{"class":132},[115,45908,3919],{"class":132},[115,45910,1996],{"class":132},[52,45912,45914],{"id":45913},"_6-run-release-tasks-that-do-not-depend-on-wsgi-or-asgi","6. Run release tasks that do not depend on WSGI or ASGI",[16,45916,45917],{},"Before switching traffic, run the normal Django production tasks:",[106,45919,45921],{"className":108,"code":45920,"language":110,"meta":111,"style":111},"source \u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Factivate\ncd \u002Fsrv\u002Fproject\u002Fcurrent\npython manage.py migrate\npython manage.py collectstatic --noinput\n",[20,45922,45923,45930,45937,45945],{"__ignoreMap":111},[115,45924,45925,45927],{"class":117,"line":118},[115,45926,5311],{"class":202},[115,45928,45929],{"class":132}," \u002Fsrv\u002Fproject\u002Fvenv\u002Fbin\u002Factivate\n",[115,45931,45932,45934],{"class":117,"line":136},[115,45933,5303],{"class":202},[115,45935,45936],{"class":132}," \u002Fsrv\u002Fproject\u002Fcurrent\n",[115,45938,45939,45941,45943],{"class":117,"line":149},[115,45940,1114],{"class":262},[115,45942,1117],{"class":132},[115,45944,11324],{"class":132},[115,45946,45947,45949,45951,45953],{"class":117,"line":162},[115,45948,1114],{"class":262},[115,45950,1117],{"class":132},[115,45952,1838],{"class":132},[115,45954,1841],{"class":202},[16,45956,45957],{},"Then verify your app locally first and through the reverse proxy:",[106,45959,45961],{"className":108,"code":45960,"language":110,"meta":111,"style":111},"# Replace \u002Fhealth\u002F with your actual health endpoint if you expose one\ncurl -I http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F\ncurl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,45962,45963,45968,45976],{"__ignoreMap":111},[115,45964,45965],{"class":117,"line":118},[115,45966,45967],{"class":3861},"# Replace \u002Fhealth\u002F with your actual health endpoint if you expose one\n",[115,45969,45970,45972,45974],{"class":117,"line":136},[115,45971,2764],{"class":262},[115,45973,2767],{"class":202},[115,45975,28560],{"class":132},[115,45977,45978,45980,45982],{"class":117,"line":149},[115,45979,2764],{"class":262},[115,45981,2767],{"class":202},[115,45983,13426],{"class":132},[52,45985,45987],{"id":45986},"_7-verify-the-deployment-after-startup","7. Verify the deployment after startup",[16,45989,45990],{},"Check service health:",[106,45992,45994],{"className":108,"code":45993,"language":110,"meta":111,"style":111},"systemctl status gunicorn\n",[20,45995,45996],{"__ignoreMap":111},[115,45997,45998,46000,46002],{"class":117,"line":118},[115,45999,1981],{"class":262},[115,46001,1984],{"class":132},[115,46003,1987],{"class":132},[16,46005,46006],{},"or:",[106,46008,46010],{"className":108,"code":46009,"language":110,"meta":111,"style":111},"systemctl status uvicorn\n",[20,46011,46012],{"__ignoreMap":111},[115,46013,46014,46016,46018],{"class":117,"line":118},[115,46015,1981],{"class":262},[115,46017,1984],{"class":132},[115,46019,12375],{"class":132},[16,46021,12724],{},[106,46023,46024],{"className":108,"code":23806,"language":110,"meta":111,"style":111},[20,46025,46026],{"__ignoreMap":111},[115,46027,46028,46030,46032,46034,46036,46038],{"class":117,"line":118},[115,46029,2785],{"class":262},[115,46031,2788],{"class":202},[115,46033,2791],{"class":132},[115,46035,2794],{"class":202},[115,46037,15523],{"class":202},[115,46039,2800],{"class":202},[16,46041,46006],{},[106,46043,46045],{"className":108,"code":46044,"language":110,"meta":111,"style":111},"journalctl -u uvicorn -n 50 --no-pager\n",[20,46046,46047],{"__ignoreMap":111},[115,46048,46049,46051,46053,46055,46057,46059],{"class":117,"line":118},[115,46050,2785],{"class":262},[115,46052,2788],{"class":202},[115,46054,31000],{"class":132},[115,46056,2794],{"class":202},[115,46058,15523],{"class":202},[115,46060,2800],{"class":202},[16,46062,46063],{},"Watch for:",[63,46065,46066,46069,46072,46076,46079,46082,46090],{},[66,46067,46068],{},"startup import errors",[66,46070,46071],{},"database connection failures",[66,46073,5071,46074],{},[20,46075,5074],{},[66,46077,46078],{},"missing environment variable parsing in your settings module",[66,46080,46081],{},"permission errors on env files or static files",[66,46083,46084,46087,46088],{},[20,46085,46086],{},"DisallowedHost"," errors from ",[20,46089,2719],{},[66,46091,46092,46094],{},[20,46093,13800],{}," errors from Nginx",[52,46096,46098],{"id":46097},"_8-keep-a-rollback-path-if-you-switch-interfaces","8. Keep a rollback path if you switch interfaces",[16,46100,46101],{},"If you move from WSGI to ASGI, do not combine that change with a risky schema migration.",[16,46103,46104],{},"A safe rollback plan is:",[1173,46106,46107,46113,46116,46119,46122,46125,46128],{},[66,46108,46109,46110,46112],{},"keep the old ",[20,46111,1277],{}," unit file",[66,46114,46115],{},"keep the previous app server command",[66,46117,46118],{},"keep the previous Nginx config",[66,46120,46121],{},"switch the service back",[66,46123,46124],{},"restart the previous service",[66,46126,46127],{},"reload Nginx",[66,46129,2868],{},[16,46131,46132],{},"Example rollback sequence:",[106,46134,46136],{"className":108,"code":46135,"language":110,"meta":111,"style":111},"sudo systemctl stop uvicorn\nsudo systemctl start gunicorn\nsudo systemctl reload nginx\ncurl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\njournalctl -u gunicorn -n 50 --no-pager\n",[20,46137,46138,46148,46158,46168,46176],{"__ignoreMap":111},[115,46139,46140,46142,46144,46146],{"class":117,"line":118},[115,46141,2001],{"class":262},[115,46143,3480],{"class":132},[115,46145,9987],{"class":132},[115,46147,12375],{"class":132},[115,46149,46150,46152,46154,46156],{"class":117,"line":136},[115,46151,2001],{"class":262},[115,46153,3480],{"class":132},[115,46155,15489],{"class":132},[115,46157,1987],{"class":132},[115,46159,46160,46162,46164,46166],{"class":117,"line":149},[115,46161,2001],{"class":262},[115,46163,3480],{"class":132},[115,46165,3919],{"class":132},[115,46167,1996],{"class":132},[115,46169,46170,46172,46174],{"class":117,"line":162},[115,46171,2764],{"class":262},[115,46173,2767],{"class":202},[115,46175,13426],{"class":132},[115,46177,46178,46180,46182,46184,46186,46188],{"class":117,"line":175},[115,46179,2785],{"class":262},[115,46181,2788],{"class":202},[115,46183,2791],{"class":132},[115,46185,2794],{"class":202},[115,46187,15523],{"class":202},[115,46189,2800],{"class":202},[16,46191,46192],{},"If the ASGI rollout also changed Nginx websocket proxying or introduced Channels\u002FRedis, include those components in the rollback plan as well.",[11,46194,1321],{"id":1320},[52,46196,46198],{"id":46197},"what-wsgi-handles-well","What WSGI handles well",[16,46200,46201],{},"WSGI is the right default for most Django production apps because it is simple and mature. If your traffic is standard HTTP request\u002Fresponse and your app logic is mostly synchronous, WSGI gives you a predictable process model and easier debugging.",[16,46203,46204],{},"A typical stack is:",[63,46206,46207,46209,46212,46218,46220],{},[66,46208,1647],{},[66,46210,46211],{},"Gunicorn sync workers",[66,46213,46214,46215],{},"Django via ",[20,46216,46217],{},"wsgi.py",[66,46219,1773],{},[66,46221,46222],{},"optional Redis for cache or task queue",[16,46224,46225],{},"This is enough for many admin systems, client portals, APIs, and content sites.",[52,46227,46229],{"id":46228},"what-asgi-adds","What ASGI adds",[16,46231,46232],{},"ASGI adds support for async protocols and long-lived connections. It matters when you need:",[63,46234,46235,46237,46240,46242,46245],{},[66,46236,44802],{},[66,46238,46239],{},"realtime browser updates",[66,46241,44808],{},[66,46243,46244],{},"async-first APIs",[66,46246,46247],{},"streaming behavior that benefits from an event loop",[16,46249,46204],{},[63,46251,46252,46254,46257,46261],{},[66,46253,1647],{},[66,46255,46256],{},"Uvicorn, or Gunicorn with Uvicorn workers",[66,46258,46214,46259],{},[20,46260,11755],{},[66,46262,46263],{},"Redis if Channels or coordination requires it",[16,46265,46266],{},"ASGI is not automatically faster. If your code still spends most of its time in synchronous database calls or blocking libraries, ASGI may add complexity without meaningful benefit.",[52,46268,46270],{"id":46269},"worker-model-and-failure-modes","Worker model and failure modes",[16,46272,46273],{},"With WSGI, a sync worker can get tied up by slow requests. Capacity planning is usually about worker count, memory use, and timeout tuning.",[16,46275,46276],{},"With ASGI, you also need to care about:",[63,46278,46279,46282,46285,46288,46291],{},[66,46280,46281],{},"blocking sync code inside async paths",[66,46283,46284],{},"websocket proxy configuration",[66,46286,46287],{},"connection counts",[66,46289,46290],{},"event loop stalls",[66,46292,46293],{},"timeout mismatches between Nginx and the app server",[16,46295,46296],{},"For both models, monitor:",[63,46298,46299,46306,46309,46312,46315],{},[66,46300,46301,3146,46303,46305],{},[20,46302,13800],{},[20,46304,32150],{}," rates",[66,46307,46308],{},"worker restarts",[66,46310,46311],{},"response times by endpoint",[66,46313,46314],{},"memory growth",[66,46316,46317],{},"connection failures in logs",[52,46319,41766],{"id":41765},[16,46321,46322,46323,46325],{},"Once you are repeating the same setup across multiple servers or environments, convert the manual steps into reusable templates. The best first targets are ",[20,46324,1277],{}," service generation, Nginx config generation, environment file checks, health verification, and rollback commands. Keep the WSGI and ASGI variants modular so you can switch app server type without rewriting the whole release process.",[11,46327,30532],{"id":30531},[63,46329,46330,46336,46342,46347,46352,46358,46373,46379],{},[66,46331,46332,46335],{},[1226,46333,46334],{},"Async views under WSGI:"," Django can run them, but you do not get the full benefit of an ASGI-native deployment.",[66,46337,46338,46341],{},[1226,46339,46340],{},"One project with both sync and async behavior:"," possible, but production should still choose one primary serving interface.",[66,46343,46344,46346],{},[1226,46345,10126],{}," neither WSGI nor ASGI should serve production static files directly; let Nginx or a CDN do it.",[66,46348,46349,46351],{},[1226,46350,27007],{}," plan media storage separately from static files. Do not assume the static file path layout is appropriate for user uploads.",[66,46353,46354,46357],{},[1226,46355,46356],{},"TLS:"," terminate TLS at Nginx or another reverse proxy, not at the Django app server.",[66,46359,46360,46363,46364,22620,46366,46368,46369,46372],{},[1226,46361,46362],{},"Security:"," keep ",[20,46365,2707],{},[20,46367,2719],{},", and bind Gunicorn\u002FUvicorn to ",[20,46370,46371],{},"127.0.0.1"," or a private interface only.",[66,46374,46375,46378],{},[1226,46376,46377],{},"Channels:"," if you use Django Channels, ASGI is required, and Redis is commonly added for the channel layer.",[66,46380,46381,46384],{},[1226,46382,46383],{},"Do not switch interfaces during a risky release:"," change the serving layer separately from large migrations so rollback stays simple.",[11,46386,1386],{"id":1385},[16,46388,46389,46390,211],{},"If you need the wider production picture, start with ",[1395,46391,46392],{"href":22931},"Django deployment architecture explained",[16,46394,46395],{},"For implementation details, see:",[63,46397,46398,46403],{},[66,46399,46400],{},[1395,46401,46402],{"href":2985},"How to deploy Django with Gunicorn and Nginx",[66,46404,46405],{},[1395,46406,46407],{"href":8038},"How to deploy Django with Uvicorn and Nginx",[16,46409,46410],{},"For production settings and release safety, also review:",[63,46412,46413,46417],{},[66,46414,46415],{},[1395,46416,3000],{"href":2999},[66,46418,46419],{},[1395,46420,3007],{"href":3006},[16,46422,46423,46424,211],{},"If the app stops responding after the change, use ",[1395,46425,46426],{"href":4455},"How to fix 502 Bad Gateway in Django behind Nginx",[11,46428,1420],{"id":1419},[52,46430,46432],{"id":46431},"is-wsgi-or-asgi-better-for-most-django-apps","Is WSGI or ASGI better for most Django apps?",[16,46434,46435,46436,46438,46439,46441],{},"For most Django apps, ",[1226,46437,44791],{}," is the better default because it is simpler to operate and fully supports normal request\u002Fresponse workloads. Use ",[1226,46440,44796],{}," when you have a clear async requirement such as websockets or realtime features.",[52,46443,46445],{"id":46444},"do-i-need-asgi-to-use-async-views-in-django","Do I need ASGI to use async views in Django?",[16,46447,46448],{},"Not always, but ASGI is the correct deployment target if async behavior is a real part of your production design. Running async views under WSGI works, but it does not provide the full ASGI execution model.",[52,46450,46452],{"id":46451},"should-i-switch-an-existing-django-app-from-wsgi-to-asgi","Should I switch an existing Django app from WSGI to ASGI?",[16,46454,46455],{},"Only if your requirements justify it. If the app is stable on WSGI and does not need websockets, streaming, or async-first behavior, switching may add complexity without operational benefit.",[52,46457,46459],{"id":46458},"can-i-run-django-channels-without-asgi","Can I run Django Channels without ASGI?",[16,46461,46462],{},"No. Django Channels requires an ASGI deployment path.",[52,46464,46466],{"id":46465},"does-gunicorn-work-with-both-wsgi-and-asgi","Does Gunicorn work with both WSGI and ASGI?",[16,46468,46469],{},"Yes. Gunicorn works directly with WSGI apps, and it can also run ASGI apps using Uvicorn workers:",[106,46471,46472],{"className":108,"code":45094,"language":110,"meta":111,"style":111},[20,46473,46474],{"__ignoreMap":111},[115,46475,46476,46478,46480,46482,46484,46486],{"class":117,"line":118},[115,46477,14954],{"class":262},[115,46479,45076],{"class":132},[115,46481,3936],{"class":202},[115,46483,45107],{"class":132},[115,46485,23605],{"class":202},[115,46487,32827],{"class":132},[16,46489,46490],{},"That makes it a practical option for teams that already use Gunicorn in production.",[1485,46492,46493],{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":46495},[46496,46497,46498,46517,46523,46524,46525],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":46499},[46500,46501,46502,46507,46511,46514,46515,46516],{"id":44848,"depth":149,"text":44849},{"id":44910,"depth":149,"text":44911},{"id":45036,"depth":149,"text":45037,"children":46503},[46504,46505,46506],{"id":45040,"depth":162,"text":45041},{"id":45063,"depth":162,"text":45064},{"id":45090,"depth":162,"text":45091},{"id":45117,"depth":149,"text":45118,"children":46508},[46509,46510],{"id":45124,"depth":162,"text":45125},{"id":45241,"depth":162,"text":45242},{"id":45571,"depth":149,"text":45572,"children":46512},[46513],{"id":45578,"depth":162,"text":45579},{"id":45913,"depth":149,"text":45914},{"id":45986,"depth":149,"text":45987},{"id":46097,"depth":149,"text":46098},{"id":1320,"depth":136,"text":1321,"children":46518},[46519,46520,46521,46522],{"id":46197,"depth":149,"text":46198},{"id":46228,"depth":149,"text":46229},{"id":46269,"depth":149,"text":46270},{"id":41765,"depth":149,"text":41766},{"id":30531,"depth":136,"text":30532},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":46526},[46527,46528,46529,46530,46531],{"id":46431,"depth":149,"text":46432},{"id":46444,"depth":149,"text":46445},{"id":46451,"depth":149,"text":46452},{"id":46458,"depth":149,"text":46459},{"id":46465,"depth":149,"text":46466},"When you deploy Django in production, you will see both WSGI and ASGI recommended.",{},"\u002Fdjango-wsgi-vs-asgi",[2985,8038,8045],{"title":3014,"description":46532},[1557,14954,12359],"django-wsgi-vs-asgi",[1557,14954,12359],"VLSzOwkKRJ1wU4Ipxc0yn51BuyE_WtIIkYqt_agBM4U",{"id":46542,"title":46543,"body":46544,"category":4543,"description":48053,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":48054,"navigation":309,"path":48055,"priority":27130,"related":48056,"role":4552,"section":4553,"seo":48057,"stack":48058,"stem":48059,"tags":48060,"type":4558,"__hash__":48061},"articles\u002Ffix-django-database-connection-errors-production.md","Fix Database Connection Errors in Django Production",{"type":8,"value":46545,"toc":48012},[46546,46548,46551,46554,46590,46593,46595,46598,46615,46620,46622,46626,46629,46633,46636,46654,46656,46681,46684,46719,46722,46754,46757,46771,46774,46779,46783,46786,46793,46796,46943,46946,46949,46964,46966,47045,47048,47072,47075,47113,47116,47121,47127,47131,47134,47138,47154,47157,47161,47179,47186,47192,47195,47227,47229,47241,47244,47267,47277,47284,47288,47291,47295,47298,47312,47318,47342,47346,47349,47367,47370,47376,47391,47398,47402,47409,47418,47421,47432,47440,47444,47447,47458,47464,47474,47477,47487,47490,47494,47499,47502,47526,47529,47565,47568,47576,47587,47591,47594,47597,47655,47658,47675,47678,47681,47685,47688,47691,47705,47708,47722,47724,47737,47739,47754,47757,47760,47763,47767,47770,47773,47791,47794,47806,47809,47823,47826,47828,47831,47848,47855,47857,47866,47868,47928,47930,47936,47945,47952,47957,47959,47963,47966,47970,47979,47986,47989,47993,48002,48006,48009],[11,46547,14],{"id":13},[16,46549,46550],{},"Database connection errors in Django production usually appear right after a deploy, after a restart, or after an infrastructure change such as rotating secrets, moving PostgreSQL, enabling SSL, or changing firewall rules. The app may fail at startup, return 500 errors under traffic, or only break on database-backed views.",[16,46552,46553],{},"Common production symptoms include:",[63,46555,46556,46561,46566,46571,46576,46581,46587],{},[66,46557,46558],{},[20,46559,46560],{},"psycopg2.OperationalError: could not connect to server",[66,46562,46563],{},[20,46564,46565],{},"connection refused",[66,46567,46568],{},[20,46569,46570],{},"connection timed out",[66,46572,46573],{},[20,46574,46575],{},"password authentication failed for user",[66,46577,46578],{},[20,46579,46580],{},"could not translate host name",[66,46582,46583,46584],{},"SSL errors such as ",[20,46585,46586],{},"no pg_hba.conf entry ... SSL off",[66,46588,46589],{},"intermittent failures from connection exhaustion",[16,46591,46592],{},"The main risk is changing Django settings too early without proving whether the problem is credentials, DNS, networking, PostgreSQL readiness, SSL requirements, or connection limits.",[11,46594,30],{"id":29},[16,46596,46597],{},"Check these in order:",[1173,46599,46600,46603,46606,46609,46612],{},[66,46601,46602],{},"identify the exact error from logs",[66,46604,46605],{},"confirm Django is reading the expected production database settings",[66,46607,46608],{},"test DNS and TCP reachability from the app host or container",[66,46610,46611],{},"test a direct PostgreSQL login with the same host, user, database, and SSL mode",[66,46613,46614],{},"verify PostgreSQL is listening, allows your source IP, and has available connections",[16,46616,46617,46618,211],{},"Do the checks from the actual app runtime environment before editing ",[20,46619,10632],{},[11,46621,43],{"id":42},[11,46623,46625],{"id":46624},"identify-the-exact-database-connection-error","Identify the exact database connection error",[16,46627,46628],{},"Start with logs. Do not guess from the browser error page.",[52,46630,46632],{"id":46631},"check-django-and-gunicornuvicorn-logs","Check Django and Gunicorn\u002FUvicorn logs",[16,46634,46635],{},"For systemd-managed Gunicorn:",[106,46637,46638],{"className":108,"code":3266,"language":110,"meta":111,"style":111},[20,46639,46640],{"__ignoreMap":111},[115,46641,46642,46644,46646,46648,46650,46652],{"class":117,"line":118},[115,46643,2785],{"class":262},[115,46645,2788],{"class":202},[115,46647,2791],{"class":132},[115,46649,2794],{"class":202},[115,46651,2797],{"class":202},[115,46653,2800],{"class":202},[16,46655,5244],{},[106,46657,46659],{"className":108,"code":46658,"language":110,"meta":111,"style":111},"docker logs \u003Ccontainer_name> --tail 100\n",[20,46660,46661],{"__ignoreMap":111},[115,46662,46663,46665,46667,46669,46672,46674,46676,46678],{"class":117,"line":118},[115,46664,3295],{"class":262},[115,46666,3301],{"class":132},[115,46668,7691],{"class":121},[115,46670,46671],{"class":132},"container_nam",[115,46673,17377],{"class":125},[115,46675,22818],{"class":121},[115,46677,38217],{"class":202},[115,46679,46680],{"class":202}," 100\n",[16,46682,46683],{},"If PostgreSQL is self-hosted and managed by systemd:",[106,46685,46687],{"className":108,"code":46686,"language":110,"meta":111,"style":111},"journalctl -u postgresql -n 100 --no-pager || journalctl -u \"postgresql*\" -n 100 --no-pager\n",[20,46688,46689],{"__ignoreMap":111},[115,46690,46691,46693,46695,46697,46699,46701,46704,46706,46708,46710,46713,46715,46717],{"class":117,"line":118},[115,46692,2785],{"class":262},[115,46694,2788],{"class":202},[115,46696,14520],{"class":132},[115,46698,2794],{"class":202},[115,46700,2797],{"class":202},[115,46702,46703],{"class":202}," --no-pager",[115,46705,43235],{"class":121},[115,46707,5030],{"class":262},[115,46709,2788],{"class":202},[115,46711,46712],{"class":132}," \"postgresql*\"",[115,46714,2794],{"class":202},[115,46716,2797],{"class":202},[115,46718,2800],{"class":202},[16,46720,46721],{},"Classify the error:",[63,46723,46724,46730,46736,46742,46748],{},[66,46725,46726,46729],{},[1226,46727,46728],{},"refused",": host reachable, but nothing accepts connections on that port",[66,46731,46732,46735],{},[1226,46733,46734],{},"timed out",": packets are blocked or the route is broken",[66,46737,46738,46741],{},[1226,46739,46740],{},"authentication failed",": username\u002Fpassword is wrong, or access is denied",[66,46743,46744,46747],{},[1226,46745,46746],{},"DNS failure",": hostname is wrong or not resolvable from production",[66,46749,46750,46753],{},[1226,46751,46752],{},"SSL error",": provider requires TLS or certificate validation is wrong",[16,46755,46756],{},"Also note whether the failure happens:",[63,46758,46759,46762,46765,46768],{},[66,46760,46761],{},"only at app startup",[66,46763,46764],{},"only during migrations",[66,46766,46767],{},"only during live traffic",[66,46769,46770],{},"intermittently after running for a while",[16,46772,46773],{},"That distinction matters later.",[16,46775,46776,46778],{},[1226,46777,3515],{}," save the exact error string before changing anything.",[11,46780,46782],{"id":46781},"confirm-django-is-using-the-expected-production-database-settings","Confirm Django is using the expected production database settings",[16,46784,46785],{},"A common cause is that the process is not loading the expected production environment variables.",[52,46787,46789,46790,46792],{"id":46788},"inspect-databases-configuration-safely","Inspect ",[20,46791,10632],{}," configuration safely",[16,46794,46795],{},"A typical Django PostgreSQL config:",[106,46797,46799],{"className":2369,"code":46798,"language":1114,"meta":111,"style":111},"import os\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": os.environ[\"DB_NAME\"],\n        \"USER\": os.environ[\"DB_USER\"],\n        \"PASSWORD\": os.environ[\"DB_PASSWORD\"],\n        \"HOST\": os.environ[\"DB_HOST\"],\n        \"PORT\": os.environ.get(\"DB_PORT\", \"5432\"),\n        \"CONN_MAX_AGE\": int(os.environ.get(\"DB_CONN_MAX_AGE\", \"60\")),\n        \"OPTIONS\": {\n            \"sslmode\": os.environ.get(\"DB_SSLMODE\", \"require\"),  # set explicitly per environment\n        },\n    }\n}\n",[20,46800,46801,46807,46811,46819,46825,46835,46845,46855,46865,46875,46889,46907,46913,46931,46935,46939],{"__ignoreMap":111},[115,46802,46803,46805],{"class":117,"line":118},[115,46804,5613],{"class":121},[115,46806,5616],{"class":125},[115,46808,46809],{"class":117,"line":136},[115,46810,310],{"emptyLinePlaceholder":309},[115,46812,46813,46815,46817],{"class":117,"line":149},[115,46814,10632],{"class":202},[115,46816,2380],{"class":121},[115,46818,2166],{"class":125},[115,46820,46821,46823],{"class":117,"line":162},[115,46822,10664],{"class":132},[115,46824,3374],{"class":125},[115,46826,46827,46829,46831,46833],{"class":117,"line":175},[115,46828,10671],{"class":132},[115,46830,2513],{"class":125},[115,46832,10676],{"class":132},[115,46834,3354],{"class":125},[115,46836,46837,46839,46841,46843],{"class":117,"line":350},[115,46838,10683],{"class":132},[115,46840,10686],{"class":125},[115,46842,10689],{"class":132},[115,46844,3430],{"class":125},[115,46846,46847,46849,46851,46853],{"class":117,"line":365},[115,46848,10696],{"class":132},[115,46850,10686],{"class":125},[115,46852,10701],{"class":132},[115,46854,3430],{"class":125},[115,46856,46857,46859,46861,46863],{"class":117,"line":380},[115,46858,10708],{"class":132},[115,46860,10686],{"class":125},[115,46862,10713],{"class":132},[115,46864,3430],{"class":125},[115,46866,46867,46869,46871,46873],{"class":117,"line":487},[115,46868,10720],{"class":132},[115,46870,10686],{"class":125},[115,46872,10725],{"class":132},[115,46874,3430],{"class":125},[115,46876,46877,46879,46881,46883,46885,46887],{"class":117,"line":2095},[115,46878,10732],{"class":132},[115,46880,10735],{"class":125},[115,46882,10738],{"class":132},[115,46884,1153],{"class":125},[115,46886,10743],{"class":132},[115,46888,10746],{"class":125},[115,46890,46891,46893,46895,46897,46899,46901,46903,46905],{"class":117,"line":2104},[115,46892,10751],{"class":132},[115,46894,2513],{"class":125},[115,46896,10756],{"class":202},[115,46898,10759],{"class":125},[115,46900,10762],{"class":132},[115,46902,1153],{"class":125},[115,46904,10767],{"class":132},[115,46906,10770],{"class":125},[115,46908,46909,46911],{"class":117,"line":2113},[115,46910,10775],{"class":132},[115,46912,3374],{"class":125},[115,46914,46915,46917,46919,46921,46923,46925,46928],{"class":117,"line":2122},[115,46916,10782],{"class":132},[115,46918,10735],{"class":125},[115,46920,10787],{"class":132},[115,46922,1153],{"class":125},[115,46924,10792],{"class":132},[115,46926,46927],{"class":125},"),  ",[115,46929,46930],{"class":3861},"# set explicitly per environment\n",[115,46932,46933],{"class":117,"line":2131},[115,46934,3398],{"class":125},[115,46936,46937],{"class":117,"line":2136},[115,46938,2233],{"class":125},[115,46940,46941],{"class":117,"line":2142},[115,46942,2323],{"class":125},[16,46944,46945],{},"Check where those values come from.",[16,46947,46948],{},"For systemd:",[106,46950,46952],{"className":2026,"code":46951,"language":2028,"meta":111,"style":111},"[Service]\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[20,46953,46954,46958],{"__ignoreMap":111},[115,46955,46956],{"class":117,"line":118},[115,46957,2060],{"class":262},[115,46959,46960,46962],{"class":117,"line":136},[115,46961,2089],{"class":121},[115,46963,4912],{"class":125},[16,46965,3285],{},[106,46967,46969],{"className":2485,"code":46968,"language":2487,"meta":111,"style":111},"services:\n  web:\n    environment:\n      DB_NAME: appdb\n      DB_USER: appuser\n      DB_PASSWORD: ${DB_PASSWORD}\n      DB_HOST: db.example.internal\n      DB_PORT: \"5432\"\n      DB_SSLMODE: require\n",[20,46970,46971,46977,46983,46989,46998,47007,47017,47027,47036],{"__ignoreMap":111},[115,46972,46973,46975],{"class":117,"line":118},[115,46974,2495],{"class":2494},[115,46976,2498],{"class":125},[115,46978,46979,46981],{"class":117,"line":136},[115,46980,2503],{"class":2494},[115,46982,2498],{"class":125},[115,46984,46985,46987],{"class":117,"line":149},[115,46986,27844],{"class":2494},[115,46988,2498],{"class":125},[115,46990,46991,46994,46996],{"class":117,"line":162},[115,46992,46993],{"class":2494},"      DB_NAME",[115,46995,2513],{"class":125},[115,46997,10420],{"class":132},[115,46999,47000,47003,47005],{"class":117,"line":175},[115,47001,47002],{"class":2494},"      DB_USER",[115,47004,2513],{"class":125},[115,47006,10431],{"class":132},[115,47008,47009,47012,47014],{"class":117,"line":350},[115,47010,47011],{"class":2494},"      DB_PASSWORD",[115,47013,2513],{"class":125},[115,47015,47016],{"class":132},"${DB_PASSWORD}\n",[115,47018,47019,47022,47024],{"class":117,"line":365},[115,47020,47021],{"class":2494},"      DB_HOST",[115,47023,2513],{"class":125},[115,47025,47026],{"class":132},"db.example.internal\n",[115,47028,47029,47032,47034],{"class":117,"line":380},[115,47030,47031],{"class":2494},"      DB_PORT",[115,47033,2513],{"class":125},[115,47035,185],{"class":132},[115,47037,47038,47041,47043],{"class":117,"line":487},[115,47039,47040],{"class":2494},"      DB_SSLMODE",[115,47042,2513],{"class":125},[115,47044,10476],{"class":132},[16,47046,47047],{},"Verify the running process sees the expected non-secret values:",[106,47049,47051],{"className":108,"code":47050,"language":110,"meta":111,"style":111},"systemctl cat gunicorn\nsystemctl show gunicorn --property=EnvironmentFiles\n",[20,47052,47053,47061],{"__ignoreMap":111},[115,47054,47055,47057,47059],{"class":117,"line":118},[115,47056,1981],{"class":262},[115,47058,4973],{"class":132},[115,47060,1987],{"class":132},[115,47062,47063,47065,47067,47069],{"class":117,"line":136},[115,47064,1981],{"class":262},[115,47066,12372],{"class":132},[115,47068,2791],{"class":132},[115,47070,47071],{"class":202}," --property=EnvironmentFiles\n",[16,47073,47074],{},"Inside a container:",[106,47076,47078],{"className":108,"code":47077,"language":110,"meta":111,"style":111},"docker exec -it \u003Capp_container> sh\nenv | grep -E '^(DB_NAME|DB_USER|DB_HOST|DB_PORT|DB_SSLMODE)='\n",[20,47079,47080,47100],{"__ignoreMap":111},[115,47081,47082,47084,47086,47088,47090,47093,47095,47097],{"class":117,"line":118},[115,47083,3295],{"class":262},[115,47085,5258],{"class":132},[115,47087,43152],{"class":202},[115,47089,7691],{"class":121},[115,47091,47092],{"class":132},"app_containe",[115,47094,31278],{"class":125},[115,47096,22818],{"class":121},[115,47098,47099],{"class":132}," sh\n",[115,47101,47102,47104,47106,47108,47110],{"class":117,"line":136},[115,47103,2331],{"class":262},[115,47105,579],{"class":121},[115,47107,4838],{"class":262},[115,47109,6482],{"class":202},[115,47111,47112],{"class":132}," '^(DB_NAME|DB_USER|DB_HOST|DB_PORT|DB_SSLMODE)='\n",[16,47114,47115],{},"Do not print passwords into shared logs or paste them into tickets.",[16,47117,47118,47120],{},[1226,47119,3515],{}," confirm host, port, database name, username, and SSL mode match your intended production database.",[16,47122,47123,47126],{},[1226,47124,47125],{},"Rollback note:"," if you edit env files, keep a copy of the last known-good version before restarting services.",[11,47128,47130],{"id":47129},"test-connectivity-from-the-application-host","Test connectivity from the application host",[16,47132,47133],{},"Test from the app host, not your laptop.",[52,47135,47137],{"id":47136},"check-dns-resolution","Check DNS resolution",[106,47139,47141],{"className":108,"code":47140,"language":110,"meta":111,"style":111},"getent hosts db.example.internal\n",[20,47142,47143],{"__ignoreMap":111},[115,47144,47145,47148,47151],{"class":117,"line":118},[115,47146,47147],{"class":262},"getent",[115,47149,47150],{"class":132}," hosts",[115,47152,47153],{"class":132}," db.example.internal\n",[16,47155,47156],{},"If this fails, the hostname is wrong or not resolvable in that environment.",[52,47158,47160],{"id":47159},"test-tcp-reachability","Test TCP reachability",[106,47162,47164],{"className":108,"code":47163,"language":110,"meta":111,"style":111},"nc -vz db.example.internal 5432\n",[20,47165,47166],{"__ignoreMap":111},[115,47167,47168,47170,47173,47176],{"class":117,"line":118},[115,47169,5352],{"class":262},[115,47171,47172],{"class":202}," -vz",[115,47174,47175],{"class":132}," db.example.internal",[115,47177,47178],{"class":202}," 5432\n",[16,47180,47181,47182,47185],{},"If you get ",[20,47183,47184],{},"succeeded",", the port is reachable. If it times out, look at firewall or routing. If it is refused, PostgreSQL may not be listening on that interface.",[52,47187,55,47189,47191],{"id":47188},"use-psql-with-the-same-parameters",[20,47190,835],{}," with the same parameters",[16,47193,47194],{},"Set the password only in the current shell:",[106,47196,47198],{"className":108,"code":47197,"language":110,"meta":111,"style":111},"export PGPASSWORD='your-password'\npsql \"host=db.example.internal port=5432 dbname=appdb user=appuser sslmode=require\"\nunset PGPASSWORD\n",[20,47199,47200,47212,47219],{"__ignoreMap":111},[115,47201,47202,47204,47207,47209],{"class":117,"line":118},[115,47203,122],{"class":121},[115,47205,47206],{"class":125}," PGPASSWORD",[115,47208,129],{"class":121},[115,47210,47211],{"class":132},"'your-password'\n",[115,47213,47214,47216],{"class":117,"line":136},[115,47215,835],{"class":262},[115,47217,47218],{"class":132}," \"host=db.example.internal port=5432 dbname=appdb user=appuser sslmode=require\"\n",[115,47220,47221,47224],{"class":117,"line":149},[115,47222,47223],{"class":202},"unset",[115,47225,47226],{"class":132}," PGPASSWORD\n",[16,47228,11310],{},[106,47230,47232],{"className":11064,"code":47231,"language":11066,"meta":111,"style":111},"SELECT current_database(), current_user;\n",[20,47233,47234],{"__ignoreMap":111},[115,47235,47236,47238],{"class":117,"line":118},[115,47237,11073],{"class":121},[115,47239,47240],{"class":125}," current_database(), current_user;\n",[16,47242,47243],{},"If you use Docker, run the test inside the container:",[106,47245,47247],{"className":108,"code":47246,"language":110,"meta":111,"style":111},"docker exec -it \u003Capp_container> sh\n",[20,47248,47249],{"__ignoreMap":111},[115,47250,47251,47253,47255,47257,47259,47261,47263,47265],{"class":117,"line":118},[115,47252,3295],{"class":262},[115,47254,5258],{"class":132},[115,47256,43152],{"class":202},[115,47258,7691],{"class":121},[115,47260,47092],{"class":132},[115,47262,31278],{"class":125},[115,47264,22818],{"class":121},[115,47266,47099],{"class":132},[16,47268,47269,47270,1153,47272,20346,47274,47276],{},"Then the same ",[20,47271,47147],{},[20,47273,5352],{},[20,47275,835],{}," checks.",[16,47278,47279,26990,47281,47283],{},[1226,47280,3515],{},[20,47282,835],{}," fails with the same error Django shows, the problem is below Django.",[11,47285,47287],{"id":47286},"verify-the-database-server-is-accepting-connections","Verify the database server is accepting connections",[16,47289,47290],{},"If the app host cannot connect, inspect PostgreSQL.",[52,47292,47294],{"id":47293},"check-postgresql-status-and-readiness","Check PostgreSQL status and readiness",[16,47296,47297],{},"On the database host for a self-hosted database:",[106,47299,47301],{"className":108,"code":47300,"language":110,"meta":111,"style":111},"systemctl status postgresql\n",[20,47302,47303],{"__ignoreMap":111},[115,47304,47305,47307,47309],{"class":117,"line":118},[115,47306,1981],{"class":262},[115,47308,1984],{"class":132},[115,47310,47311],{"class":132}," postgresql\n",[16,47313,42059,47314,47317],{},[20,47315,47316],{},"pg_isready"," from the app host or container when testing network reachability; run it on the DB host only when validating local service readiness.",[106,47319,47321],{"className":108,"code":47320,"language":110,"meta":111,"style":111},"pg_isready -h \u003Cdb_host> -p 5432\n",[20,47322,47323],{"__ignoreMap":111},[115,47324,47325,47327,47329,47331,47334,47336,47338,47340],{"class":117,"line":118},[115,47326,47316],{"class":262},[115,47328,992],{"class":202},[115,47330,7691],{"class":121},[115,47332,47333],{"class":132},"db_hos",[115,47335,30460],{"class":125},[115,47337,22818],{"class":121},[115,47339,1001],{"class":202},[115,47341,47178],{"class":202},[52,47343,47345],{"id":47344},"confirm-postgresql-is-listening-correctly","Confirm PostgreSQL is listening correctly",[16,47347,47348],{},"On the database host:",[106,47350,47352],{"className":108,"code":47351,"language":110,"meta":111,"style":111},"ss -ltn | grep 5432\n",[20,47353,47354],{"__ignoreMap":111},[115,47355,47356,47358,47361,47363,47365],{"class":117,"line":118},[115,47357,6472],{"class":262},[115,47359,47360],{"class":202}," -ltn",[115,47362,579],{"class":121},[115,47364,4838],{"class":262},[115,47366,47178],{"class":202},[16,47368,47369],{},"If you need to map the socket to a process, run the same command with elevated privileges.",[16,47371,3523,47372,47375],{},[20,47373,47374],{},"postgresql.conf"," for a deliberate listen address, for example:",[106,47377,47379],{"className":8444,"code":47378,"language":8446,"meta":111,"style":111},"listen_addresses = '10.0.0.10,127.0.0.1'\nport = 5432\n",[20,47380,47381,47386],{"__ignoreMap":111},[115,47382,47383],{"class":117,"line":118},[115,47384,47385],{},"listen_addresses = '10.0.0.10,127.0.0.1'\n",[115,47387,47388],{"class":117,"line":136},[115,47389,47390],{},"port = 5432\n",[16,47392,47393,47394,47397],{},"Avoid ",[20,47395,47396],{},"listen_addresses='*'"," unless the database is restricted by private networking and firewall rules.",[52,47399,47401],{"id":47400},"check-access-rules","Check access rules",[16,47403,47404,47405,47408],{},"For self-hosted PostgreSQL, review ",[20,47406,47407],{},"pg_hba.conf",". Example:",[106,47410,47412],{"className":8444,"code":47411,"language":8446,"meta":111,"style":111},"host    appdb    appuser    10.0.0.0\u002F24    scram-sha-256\n",[20,47413,47414],{"__ignoreMap":111},[115,47415,47416],{"class":117,"line":118},[115,47417,47411],{},[16,47419,47420],{},"For managed databases, review network access rules, security groups, VPC rules, or allowlists.",[16,47422,47423,47425,47426,47428,47429,47431],{},[1226,47424,3515],{}," after any access rule change, rerun ",[20,47427,47316],{}," and the ",[20,47430,835],{}," login from the app environment.",[16,47433,47434,47436,47437,47439],{},[1226,47435,47125],{}," document any firewall or ",[20,47438,47407],{}," change so it can be reversed if you widened access too far.",[11,47441,47443],{"id":47442},"fix-credential-and-authentication-errors","Fix credential and authentication errors",[16,47445,47446],{},"If logs show authentication failure:",[63,47448,47449,47452,47455],{},[66,47450,47451],{},"confirm the username is correct",[66,47453,47454],{},"confirm the password was rotated everywhere",[66,47456,47457],{},"confirm the user can access the target database",[16,47459,47460,47461,47463],{},"Special characters often break ",[20,47462,191],{}," parsing or shell commands if quoted incorrectly. Keep environment files simple and avoid trailing spaces.",[16,47465,47466,47467,47469,47470,47473],{},"Re-test with ",[20,47468,835],{},", but avoid storing the password in shell history. Prefer a temporary ",[20,47471,47472],{},"PGPASSWORD"," export in the current session rather than passing the secret inline in a saved command.",[16,47475,47476],{},"If needed, validate access from PostgreSQL:",[106,47478,47479],{"className":11064,"code":47231,"language":11066,"meta":111,"style":111},[20,47480,47481],{"__ignoreMap":111},[115,47482,47483,47485],{"class":117,"line":118},[115,47484,11073],{"class":121},[115,47486,47240],{"class":125},[16,47488,47489],{},"If the login works to one database but not another, the user or database mapping is wrong.",[11,47491,47493],{"id":47492},"fix-ssl-and-managed-database-connection-issues","Fix SSL and managed database connection issues",[16,47495,47496,47497,211],{},"Managed PostgreSQL often requires SSL. A frequent production case is correct host and credentials, but missing ",[20,47498,10924],{},[16,47500,47501],{},"Example Django config:",[106,47503,47504],{"className":2369,"code":10966,"language":1114,"meta":111,"style":111},[20,47505,47506,47512,47522],{"__ignoreMap":111},[115,47507,47508,47510],{"class":117,"line":118},[115,47509,10853],{"class":132},[115,47511,3374],{"class":125},[115,47513,47514,47516,47518,47520],{"class":117,"line":136},[115,47515,10979],{"class":132},[115,47517,2513],{"class":125},[115,47519,10792],{"class":132},[115,47521,3354],{"class":125},[115,47523,47524],{"class":117,"line":149},[115,47525,2323],{"class":125},[16,47527,47528],{},"If your provider requires certificate verification:",[106,47530,47532],{"className":2369,"code":47531,"language":1114,"meta":111,"style":111},"\"OPTIONS\": {\n    \"sslmode\": \"verify-full\",\n    \"sslrootcert\": \"\u002Fetc\u002Fssl\u002Fcerts\u002Fdb-ca.pem\",\n}\n",[20,47533,47534,47540,47550,47561],{"__ignoreMap":111},[115,47535,47536,47538],{"class":117,"line":118},[115,47537,10853],{"class":132},[115,47539,3374],{"class":125},[115,47541,47542,47544,47546,47548],{"class":117,"line":136},[115,47543,10979],{"class":132},[115,47545,2513],{"class":125},[115,47547,11012],{"class":132},[115,47549,3354],{"class":125},[115,47551,47552,47554,47556,47559],{"class":117,"line":149},[115,47553,11019],{"class":132},[115,47555,2513],{"class":125},[115,47557,47558],{"class":132},"\"\u002Fetc\u002Fssl\u002Fcerts\u002Fdb-ca.pem\"",[115,47560,3354],{"class":125},[115,47562,47563],{"class":117,"line":162},[115,47564,2323],{"class":125},[16,47566,47567],{},"Make sure the CA file exists and is readable by the app user.",[16,47569,47570,47571,3146,47573,47575],{},"If your provider requires client certificates, add the provider-specific ",[20,47572,11489],{},[20,47574,11492],{}," settings as documented for libpq and your PostgreSQL service.",[16,47577,47578,47580,47581,47583,47584,47586],{},[1226,47579,3515],{}," rerun ",[20,47582,835],{}," with the same ",[20,47585,10924],{}," and certificate settings before restarting the app.",[11,47588,47590],{"id":47589},"fix-connection-limit-and-stale-connection-problems","Fix connection limit and stale connection problems",[16,47592,47593],{},"If the app works briefly and then fails, check for exhausted connections.",[16,47595,47596],{},"In PostgreSQL:",[106,47598,47600],{"className":11064,"code":47599,"language":11066,"meta":111,"style":111},"SELECT count(*) FROM pg_stat_activity;\nSELECT state, count(*) FROM pg_stat_activity GROUP BY state;\nSHOW max_connections;\n",[20,47601,47602,47620,47650],{"__ignoreMap":111},[115,47603,47604,47606,47609,47611,47613,47615,47617],{"class":117,"line":118},[115,47605,11073],{"class":121},[115,47607,47608],{"class":202}," count",[115,47610,37145],{"class":125},[115,47612,39937],{"class":121},[115,47614,18281],{"class":125},[115,47616,11089],{"class":121},[115,47618,47619],{"class":125}," pg_stat_activity;\n",[115,47621,47622,47624,47627,47629,47632,47634,47636,47638,47640,47643,47646,47648],{"class":117,"line":136},[115,47623,11073],{"class":121},[115,47625,47626],{"class":121}," state",[115,47628,1153],{"class":125},[115,47630,47631],{"class":202},"count",[115,47633,37145],{"class":125},[115,47635,39937],{"class":121},[115,47637,18281],{"class":125},[115,47639,11089],{"class":121},[115,47641,47642],{"class":125}," pg_stat_activity ",[115,47644,47645],{"class":121},"GROUP BY",[115,47647,47626],{"class":121},[115,47649,3811],{"class":125},[115,47651,47652],{"class":117,"line":149},[115,47653,47654],{"class":125},"SHOW max_connections;\n",[16,47656,47657],{},"Review Django and Gunicorn together:",[63,47659,47660,47663,47669],{},[66,47661,47662],{},"each worker process can open database connections",[66,47664,47665,47666,47668],{},"higher ",[20,47667,10933],{}," can reduce reconnect churn, but also keeps connections open longer",[66,47670,47671,47672],{},"too many workers against a small database can exhaust ",[20,47673,47674],{},"max_connections",[16,47676,47677],{},"If Gunicorn is set too high for database capacity, reduce workers or add a pooler such as PgBouncer where appropriate.",[16,47679,47680],{},"This problem is common after scaling app workers without scaling database capacity.",[11,47682,47684],{"id":47683},"fix-startup-ordering-and-deploy-timing-issues","Fix startup ordering and deploy timing issues",[16,47686,47687],{},"Sometimes the database is healthy, but the app starts before it is reachable.",[16,47689,47690],{},"Typical cases:",[63,47692,47693,47696,47699,47702],{},[66,47694,47695],{},"PostgreSQL service still starting",[66,47697,47698],{},"containerized database not yet ready",[66,47700,47701],{},"migrations run before connectivity exists",[66,47703,47704],{},"app health checks begin too early",[16,47706,47707],{},"A safe restart sequence is:",[1173,47709,47710,47715,47720],{},[66,47711,47712,47713],{},"confirm database readiness with ",[20,47714,47316],{},[66,47716,47717,47718],{},"confirm direct login with ",[20,47719,835],{},[66,47721,32000],{},[16,47723,38745],{},[106,47725,47727],{"className":108,"code":47726,"language":110,"meta":111,"style":111},"systemctl restart gunicorn\n",[20,47728,47729],{"__ignoreMap":111},[115,47730,47731,47733,47735],{"class":117,"line":118},[115,47732,1981],{"class":262},[115,47734,3483],{"class":132},[115,47736,1987],{"class":132},[16,47738,36485],{},[106,47740,47742],{"className":108,"code":47741,"language":110,"meta":111,"style":111},"docker compose restart web\n",[20,47743,47744],{"__ignoreMap":111},[115,47745,47746,47748,47750,47752],{"class":117,"line":118},[115,47747,3295],{"class":262},[115,47749,3298],{"class":132},[115,47751,3483],{"class":132},[115,47753,3510],{"class":132},[16,47755,47756],{},"Avoid repeated blind restarts during an incident, especially with many app workers, because reconnect storms can make connection-limit problems worse.",[16,47758,47759],{},"If the issue started after deploy, verify whether a new release changed env loading, startup commands, or migration timing.",[16,47761,47762],{},"Also separate connectivity failures from schema problems: if the database connection succeeds but requests fail after deploy, check whether migrations were applied correctly before treating it as a connection issue.",[11,47764,47766],{"id":47765},"verify-the-fix-safely-in-production","Verify the fix safely in production",[16,47768,47769],{},"Once the direct database test passes, verify from Django with a minimal read-only check.",[16,47771,47772],{},"Minimal query:",[106,47774,47776],{"className":108,"code":47775,"language":110,"meta":111,"style":111},"python manage.py shell -c \"from django.db import connection; cursor = connection.cursor(); cursor.execute('SELECT 1'); print(cursor.fetchone())\"\n",[20,47777,47778],{"__ignoreMap":111},[115,47779,47780,47782,47784,47786,47788],{"class":117,"line":118},[115,47781,1114],{"class":262},[115,47783,1117],{"class":132},[115,47785,9071],{"class":132},[115,47787,1024],{"class":202},[115,47789,47790],{"class":132}," \"from django.db import connection; cursor = connection.cursor(); cursor.execute('SELECT 1'); print(cursor.fetchone())\"\n",[16,47792,47793],{},"Optional database shell check, only if the runtime has the PostgreSQL client installed and the app environment is loaded correctly:",[106,47795,47796],{"className":108,"code":38293,"language":110,"meta":111,"style":111},[20,47797,47798],{"__ignoreMap":111},[115,47799,47800,47802,47804],{"class":117,"line":118},[115,47801,1114],{"class":262},[115,47803,1117],{"class":132},[115,47805,32717],{"class":132},[16,47807,47808],{},"Additional deploy checks:",[106,47810,47811],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,47812,47813],{"__ignoreMap":111},[115,47814,47815,47817,47819,47821],{"class":117,"line":118},[115,47816,1114],{"class":262},[115,47818,1117],{"class":132},[115,47820,1814],{"class":132},[115,47822,1817],{"class":202},[16,47824,47825],{},"Then test one real read-only app path or health endpoint if available. Watch logs and error rate for several minutes to catch intermittent failures.",[11,47827,1321],{"id":1320},[16,47829,47830],{},"This runbook works because it isolates the layers in the order they fail:",[1173,47832,47833,47836,47839,47842,47845],{},[66,47834,47835],{},"application logs identify the error class",[66,47837,47838],{},"runtime config proves what Django is actually using",[66,47840,47841],{},"host-level tests separate Django problems from network problems",[66,47843,47844],{},"PostgreSQL checks confirm service readiness and access control",[66,47846,47847],{},"capacity checks catch issues that only appear under worker load",[16,47849,47850,47851,47854],{},"The main alternative is jumping straight into Django settings changes, which often hides the real cause. For example, changing ",[20,47852,47853],{},"HOST"," will not fix a security group block, and changing passwords will not fix required SSL.",[52,47856,41766],{"id":41765},[16,47858,47859,47860,47862,47863,47865],{},"If your team repeats these checks during every deploy or incident, convert them into a reusable script or deployment template. Good first candidates are environment validation, ",[20,47861,47316],{}," checks, a ",[20,47864,835],{}," smoke test, and post-deploy health checks that confirm a database-backed endpoint works. That reduces risky manual changes during outages.",[11,47867,10095],{"id":10094},[63,47869,47870,47876,47882,47888,47896,47902,47908,47922],{},[66,47871,47872,47875],{},[1226,47873,47874],{},"IPv4 vs IPv6:"," a hostname may resolve to IPv6 first in production, while PostgreSQL only listens on IPv4.",[66,47877,47878,47881],{},[1226,47879,47880],{},"Unix socket vs TCP:"," local environments may connect through a socket, but production uses TCP with different auth rules.",[66,47883,47884,47887],{},[1226,47885,47886],{},"Read replica mistakes:"," the app may be pointed at a replica that rejects writes or has different access controls.",[66,47889,47890,2957,47893,47895],{},[1226,47891,47892],{},"Container service names:",[20,47894,20420],{}," may work in Compose locally but not in another network or orchestration setup.",[66,47897,47898,47901],{},[1226,47899,47900],{},"Idle timeout layers:"," NAT gateways, proxies, or managed network layers can drop long-lived idle connections.",[66,47903,47904,47907],{},[1226,47905,47906],{},"Migrations after deploy:"," if the app boots before migrations finish, some errors may look like connection problems but are actually schema mismatches. Check both.",[66,47909,47910,2957,47913,1153,47915,47917,47918,47921],{},[1226,47911,47912],{},"Minimal container images:",[20,47914,835],{},[20,47916,5352],{},", or ",[20,47919,47920],{},"dbshell"," may be missing even when database connectivity is fine. Install the client tools or test from a debug container in the same network.",[66,47923,47924,47927],{},[1226,47925,47926],{},"Keep DB changes minimal during incident response:"," avoid broad PostgreSQL tuning changes until basic connectivity is stable.",[11,47929,1386],{"id":1385},[16,47931,47932,47933,211],{},"For safe secret and environment handling, see ",[1395,47934,47935],{"href":3006},"Django production environment variables",[16,47937,47938,47939,3146,47942,211],{},"If you need the full stack wiring, review ",[1395,47940,47941],{"href":1403},"deploy Django with PostgreSQL, Gunicorn, and Nginx",[1395,47943,47944],{"href":1403},"configure PostgreSQL for Django production",[16,47946,47947,47948,47951],{},"If the issue started right after a release, use ",[1395,47949,47950],{"href":1415},"rollback a Django deployment safely"," to revert app or config changes without guessing.",[16,47953,47954,47955,211],{},"For broader release hardening, review the ",[1395,47956,32365],{"href":2999},[11,47958,1420],{"id":1419},[52,47960,47962],{"id":47961},"why-does-django-connect-locally-but-fail-in-production","Why does Django connect locally but fail in production?",[16,47964,47965],{},"Local setups often use different hostnames, socket connections, relaxed auth, no SSL, or no firewall restrictions. Production usually adds remote networking, stricter access rules, secret loading, and managed database TLS requirements.",[52,47967,47969],{"id":47968},"what-does-connection-refused-vs-timeout-mean","What does “connection refused” vs “timeout” mean?",[16,47971,47972,47974,47975,47978],{},[20,47973,46565],{}," usually means the host is reachable but nothing is listening on that port, or the service rejects the connection immediately. ",[20,47976,47977],{},"timeout"," usually means packets are being dropped by a firewall, route, or network policy.",[52,47980,47982,47983,47985],{"id":47981},"should-i-set-conn_max_age-to-a-high-value","Should I set ",[20,47984,10933],{}," to a high value?",[16,47987,47988],{},"Not automatically. Persistent connections can reduce reconnect overhead, but they also keep connections open longer. Set it based on database capacity, worker count, and any idle timeout behavior in your network path.",[52,47990,47992],{"id":47991},"how-do-i-test-database-access-without-exposing-credentials","How do I test database access without exposing credentials?",[16,47994,47995,47996,47998,47999,48001],{},"Run the test from the app host, export ",[20,47997,47472],{}," only in the current shell, use ",[20,48000,835],{}," with the same connection parameters as Django, and unset the variable afterward. Avoid echoing secrets into logs, screenshots, shared terminals, or shell history.",[52,48003,48005],{"id":48004},"when-should-i-add-a-connection-pooler","When should I add a connection pooler?",[16,48007,48008],{},"Add a pooler when worker count, concurrency, or many short-lived requests make PostgreSQL connection limits a recurring issue. It is usually more useful after you have confirmed the problem is connection pressure rather than DNS, auth, or SSL misconfiguration.",[1485,48010,48011],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":48013},[48014,48015,48016,48017,48020,48024,48030,48035,48036,48037,48038,48039,48040,48043,48044,48045],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":46624,"depth":136,"text":46625,"children":48018},[48019],{"id":46631,"depth":149,"text":46632},{"id":46781,"depth":136,"text":46782,"children":48021},[48022],{"id":46788,"depth":149,"text":48023},"Inspect DATABASES configuration safely",{"id":47129,"depth":136,"text":47130,"children":48025},[48026,48027,48028],{"id":47136,"depth":149,"text":47137},{"id":47159,"depth":149,"text":47160},{"id":47188,"depth":149,"text":48029},"Use psql with the same parameters",{"id":47286,"depth":136,"text":47287,"children":48031},[48032,48033,48034],{"id":47293,"depth":149,"text":47294},{"id":47344,"depth":149,"text":47345},{"id":47400,"depth":149,"text":47401},{"id":47442,"depth":136,"text":47443},{"id":47492,"depth":136,"text":47493},{"id":47589,"depth":136,"text":47590},{"id":47683,"depth":136,"text":47684},{"id":47765,"depth":136,"text":47766},{"id":1320,"depth":136,"text":1321,"children":48041},[48042],{"id":41765,"depth":149,"text":41766},{"id":10094,"depth":136,"text":10095},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":48046},[48047,48048,48049,48051,48052],{"id":47961,"depth":149,"text":47962},{"id":47968,"depth":149,"text":47969},{"id":47981,"depth":149,"text":48050},"Should I set CONN_MAX_AGE to a high value?",{"id":47991,"depth":149,"text":47992},{"id":48004,"depth":149,"text":48005},"Database connection errors in Django production usually appear right after a deploy, after a restart, or after an infrastructure change such as rotating secrets, moving PostgreS...",{},"\u002Ffix-django-database-connection-errors-production",[40127,4455,6332],{"title":46543,"description":48053},[1557,1558],"fix-django-database-connection-errors-production",[1557,1558],"S3AVzS5WAUdziuLtmM27Tg2QxVzxl2ZP9Q-TrZrBrH8",{"id":48063,"title":48064,"body":48065,"category":44534,"description":48821,"difficulty":1543,"extension":1544,"funnel_stage":48822,"intent":4546,"meta":48823,"navigation":309,"path":48824,"priority":14025,"related":48825,"role":4552,"section":4553,"seo":48827,"stack":48828,"stem":48829,"tags":48830,"type":1561,"__hash__":48832},"articles\u002Fdjango-media-files-not-serving-uploads-broken.md","Fix Django Media Files Not Serving (Uploads Broken in Production)",{"type":8,"value":48066,"toc":48788},[48067,48070,48081,48084,48087,48089,48093,48095,48120,48123,48134,48137,48139,48143,48146,48154,48157,48163,48165,48169,48172,48179,48183,48206,48209,48213,48225,48228,48233,48236,48244,48248,48251,48259,48264,48269,48274,48279,48283,48286,48300,48303,48306,48331,48334,48344,48348,48360,48362,48377,48381,48412,48415,48417,48421,48423,48427,48430,48443,48447,48449,48471,48475,48477,48494,48498,48501,48504,48508,48521,48523,48527,48531,48546,48550,48557,48561,48578,48582,48591,48606,48610,48653,48655,48673,48675,48679,48697,48699,48702,48706,48714,48718,48721,48734,48736,48739,48743,48746,48754,48758,48761,48764,48774,48777,48779,48782,48785],[16,48068,48069],{},"If your Django app uploads files successfully but:",[63,48071,48072,48075,48078],{},[66,48073,48074],{},"Images don’t load",[66,48076,48077],{},"User uploads return 404",[66,48079,48080],{},"Media URLs are broken",[16,48082,48083],{},"👉 Then your media files are not being served correctly",[16,48085,48086],{},"This guide will walk you through how to fix it step-by-step.",[23099,48088],{},[11,48090,48092],{"id":48091},"quick-fix-try-this-first","⚡ Quick Fix (Try This First)",[16,48094,33361],{},[106,48096,48098],{"className":108,"code":48097,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn\nsudo systemctl restart nginx\n",[20,48099,48100,48110],{"__ignoreMap":111},[115,48101,48102,48104,48106,48108],{"class":117,"line":118},[115,48103,2001],{"class":262},[115,48105,3480],{"class":132},[115,48107,3483],{"class":132},[115,48109,1987],{"class":132},[115,48111,48112,48114,48116,48118],{"class":117,"line":136},[115,48113,2001],{"class":262},[115,48115,3480],{"class":132},[115,48117,3483],{"class":132},[115,48119,1996],{"class":132},[16,48121,48122],{},"Then check a media file URL:",[106,48124,48128],{"className":48125,"code":48126,"language":48127,"meta":111,"style":111},"language-console shiki shiki-themes github-light github-dark","http:\u002F\u002Fyour-domain\u002Fmedia\u002Fyour-file.jpg\n","console",[20,48129,48130],{"__ignoreMap":111},[115,48131,48132],{"class":117,"line":118},[115,48133,48126],{},[16,48135,48136],{},"👉 If still broken, continue below.",[23099,48138],{},[11,48140,48142],{"id":48141},"whats-happening","🧠 What’s Happening",[16,48144,48145],{},"In production:",[63,48147,48148,48151],{},[66,48149,48150],{},"Django does NOT serve media files",[66,48152,48153],{},"Nginx must serve them",[16,48155,48156],{},"If uploads are broken, it usually means:",[48158,48159,48160],"blockquote",{},[16,48161,48162],{},"Nginx cannot find or access your media directory",[23099,48164],{},[11,48166,48168],{"id":48167},"step-by-step-diagnosis","🧪 Step-by-Step Diagnosis",[16,48170,48171],{},"Follow these steps in order.",[52,48173,48175,48176,48178],{"id":48174},"_1-check-media_root-setting","1. Check ",[20,48177,15214],{}," setting",[16,48180,8834,48181,241],{},[20,48182,10342],{},[106,48184,48186],{"className":2369,"code":48185,"language":1114,"meta":111,"style":111},"MEDIA_ROOT = '\u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F'\nMEDIA_URL = '\u002Fmedia\u002F'\n",[20,48187,48188,48197],{"__ignoreMap":111},[115,48189,48190,48192,48194],{"class":117,"line":118},[115,48191,15214],{"class":202},[115,48193,2380],{"class":121},[115,48195,48196],{"class":132}," '\u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F'\n",[115,48198,48199,48201,48203],{"class":117,"line":136},[115,48200,15204],{"class":202},[115,48202,2380],{"class":121},[115,48204,48205],{"class":132}," '\u002Fmedia\u002F'\n",[16,48207,48208],{},"👉 This defines where uploaded files are stored",[52,48210,48212],{"id":48211},"_2-verify-files-exist","2. Verify files exist",[106,48214,48216],{"className":108,"code":48215,"language":110,"meta":111,"style":111},"ls \u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F\n",[20,48217,48218],{"__ignoreMap":111},[115,48219,48220,48222],{"class":117,"line":118},[115,48221,532],{"class":262},[115,48223,48224],{"class":132}," \u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F\n",[16,48226,48227],{},"You should see:",[63,48229,48230],{},[66,48231,48232],{},"Uploaded images\u002Ffiles",[16,48234,48235],{},"👉 If empty:",[63,48237,48238,48241],{},[66,48239,48240],{},"If not, then uploads are not working. Check your upload code.",[66,48242,48243],{},"Upload process is broken (not Nginx)",[52,48245,48247],{"id":48246},"_3-test-media-url-directly","3. Test media URL directly",[16,48249,48250],{},"Open in browser:",[106,48252,48253],{"className":48125,"code":48126,"language":48127,"meta":111,"style":111},[20,48254,48255],{"__ignoreMap":111},[115,48256,48257],{"class":117,"line":118},[115,48258,48126],{},[16,48260,48261],{},[1226,48262,48263],{},"If 404:",[63,48265,48266],{},[66,48267,48268],{},"Nginx is not serving media",[16,48270,48271],{},[1226,48272,48273],{},"If works:",[63,48275,48276],{},[66,48277,48278],{},"Problem is elsewhere (templates, paths)",[52,48280,48282],{"id":48281},"_4-check-nginx-configuration","4. Check Nginx configuration",[16,48284,48285],{},"Open config:",[106,48287,48289],{"className":108,"code":48288,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyproject\n",[20,48290,48291],{"__ignoreMap":111},[115,48292,48293,48295,48297],{"class":117,"line":118},[115,48294,2001],{"class":262},[115,48296,12408],{"class":132},[115,48298,48299],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyproject\n",[16,48301,48302],{},"or your favorite editor.",[16,48304,48305],{},"Ensure this exists:",[106,48307,48309],{"className":2154,"code":48308,"language":2156,"meta":111,"style":111},"location \u002Fmedia\u002F {\n    root \u002Fvar\u002Fwww\u002Fmyproject;\n}\n",[20,48310,48311,48319,48327],{"__ignoreMap":111},[115,48312,48313,48315,48317],{"class":117,"line":118},[115,48314,7128],{"class":121},[115,48316,2244],{"class":262},[115,48318,2220],{"class":125},[115,48320,48321,48324],{"class":117,"line":136},[115,48322,48323],{"class":121},"    root ",[115,48325,48326],{"class":125},"\u002Fvar\u002Fwww\u002Fmyproject;\n",[115,48328,48329],{"class":117,"line":149},[115,48330,2323],{"class":125},[16,48332,48333],{},"👉 Important:",[63,48335,48336],{},[66,48337,48338,48340,48341],{},[20,48339,13085],{}," maps to ",[20,48342,48343],{},"\u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F",[52,48345,48347],{"id":48346},"_5-test-nginx-config","5. Test Nginx config",[106,48349,48350],{"className":108,"code":4271,"language":110,"meta":111,"style":111},[20,48351,48352],{"__ignoreMap":111},[115,48353,48354,48356,48358],{"class":117,"line":118},[115,48355,2001],{"class":262},[115,48357,3906],{"class":132},[115,48359,4282],{"class":202},[16,48361,5144],{},[106,48363,48365],{"className":108,"code":48364,"language":110,"meta":111,"style":111},"sudo systemctl restart nginx\n",[20,48366,48367],{"__ignoreMap":111},[115,48368,48369,48371,48373,48375],{"class":117,"line":118},[115,48370,2001],{"class":262},[115,48372,3480],{"class":132},[115,48374,3483],{"class":132},[115,48376,1996],{"class":132},[52,48378,48380],{"id":48379},"_6-check-file-permissions","6. Check file permissions",[106,48382,48384],{"className":108,"code":48383,"language":110,"meta":111,"style":111},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyproject\nsudo chmod -R 755 \u002Fvar\u002Fwww\u002Fmyproject\n",[20,48385,48386,48399],{"__ignoreMap":111},[115,48387,48388,48390,48392,48394,48396],{"class":117,"line":118},[115,48389,2001],{"class":262},[115,48391,6733],{"class":132},[115,48393,6736],{"class":202},[115,48395,6739],{"class":132},[115,48397,48398],{"class":132}," \u002Fvar\u002Fwww\u002Fmyproject\n",[115,48400,48401,48403,48405,48407,48410],{"class":117,"line":136},[115,48402,2001],{"class":262},[115,48404,12480],{"class":132},[115,48406,6736],{"class":202},[115,48408,48409],{"class":202}," 755",[115,48411,48398],{"class":132},[16,48413,48414],{},"👉 Nginx must be able to read media files",[23099,48416],{},[11,48418,48420],{"id":48419},"common-causes-and-fixes","🔥 Common Causes (and Fixes)",[23099,48422],{},[52,48424,48426],{"id":48425},"media_root-is-incorrect","🔴 MEDIA_ROOT is incorrect",[16,48428,48429],{},"Fix:",[106,48431,48433],{"className":108,"code":48432,"language":110,"meta":111,"style":111},"MEDIA_ROOT = '\u002Fvar\u002Fwww\u002Fmyproject\u002Fmedia\u002F'\n",[20,48434,48435],{"__ignoreMap":111},[115,48436,48437,48439,48441],{"class":117,"line":118},[115,48438,15214],{"class":262},[115,48440,2380],{"class":132},[115,48442,48196],{"class":132},[52,48444,48446],{"id":48445},"nginx-not-configured-for-media","🔴 Nginx not configured for media",[16,48448,27398],{},[106,48450,48451],{"className":2154,"code":48308,"language":2156,"meta":111,"style":111},[20,48452,48453,48461,48467],{"__ignoreMap":111},[115,48454,48455,48457,48459],{"class":117,"line":118},[115,48456,7128],{"class":121},[115,48458,2244],{"class":262},[115,48460,2220],{"class":125},[115,48462,48463,48465],{"class":117,"line":136},[115,48464,48323],{"class":121},[115,48466,48326],{"class":125},[115,48468,48469],{"class":117,"line":149},[115,48470,2323],{"class":125},[52,48472,48474],{"id":48473},"permissions-issue","🔴 Permissions issue",[16,48476,48429],{},[106,48478,48480],{"className":108,"code":48479,"language":110,"meta":111,"style":111},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyproject\n",[20,48481,48482],{"__ignoreMap":111},[115,48483,48484,48486,48488,48490,48492],{"class":117,"line":118},[115,48485,2001],{"class":262},[115,48487,6733],{"class":132},[115,48489,6736],{"class":202},[115,48491,6739],{"class":132},[115,48493,48398],{"class":132},[52,48495,48497],{"id":48496},"files-stored-in-wrong-location","🔴 Files stored in wrong location",[16,48499,48500],{},"Check upload path:",[16,48502,48503],{},"Django might be saving elsewhere",[52,48505,48507],{"id":48506},"media_url-incorrect","🔴 MEDIA_URL incorrect",[106,48509,48511],{"className":2369,"code":48510,"language":1114,"meta":111,"style":111},"MEDIA_URL = '\u002Fmedia\u002F'\n",[20,48512,48513],{"__ignoreMap":111},[115,48514,48515,48517,48519],{"class":117,"line":118},[115,48516,15204],{"class":202},[115,48518,2380],{"class":121},[115,48520,48205],{"class":132},[23099,48522],{},[11,48524,48526],{"id":48525},"debugging-tips","🧠 Debugging Tips",[52,48528,48530],{"id":48529},"check-nginx-logs","Check Nginx logs",[106,48532,48534],{"className":108,"code":48533,"language":110,"meta":111,"style":111},"sudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,48535,48536],{"__ignoreMap":111},[115,48537,48538,48540,48542,48544],{"class":117,"line":118},[115,48539,2001],{"class":262},[115,48541,13188],{"class":132},[115,48543,2777],{"class":202},[115,48545,13195],{"class":132},[16,48547,48548],{},[1226,48549,31301],{},[63,48551,48552,48554],{},[66,48553,19897],{},[66,48555,48556],{},"permission denied",[52,48558,48560],{"id":48559},"check-browser-devtools","Check browser DevTools",[63,48562,48563,48566],{},[66,48564,48565],{},"Network tab → failed requests",[66,48567,48568,48570],{},[1226,48569,31301],{},[63,48571,48572,48575],{},[66,48573,48574],{},"404 errors",[66,48576,48577],{},"incorrect URLs",[52,48579,48581],{"id":48580},"check-django-upload-path","Check Django upload path",[16,48583,48584,48585,4493,48588,241],{},"If using ",[20,48586,48587],{},"ImageField",[20,48589,48590],{},"FileField",[106,48592,48594],{"className":2369,"code":48593,"language":1114,"meta":111,"style":111},"upload_to='uploads\u002F'\n",[20,48595,48596],{"__ignoreMap":111},[115,48597,48598,48601,48603],{"class":117,"line":118},[115,48599,48600],{"class":125},"upload_to",[115,48602,129],{"class":121},[115,48604,48605],{"class":132},"'uploads\u002F'\n",[11,48607,48609],{"id":48608},"quick-fix-checklist","✅ Quick Fix Checklist",[63,48611,48614,48623,48629,48635,48641,48647],{"className":48612},[48613],"contains-task-list",[66,48615,48618,48622],{"className":48616},[48617],"task-list-item",[48619,48620],"input",{"disabled":309,"type":48621},"checkbox"," MEDIA_ROOT is correct",[66,48624,48626,48628],{"className":48625},[48617],[48619,48627],{"disabled":309,"type":48621}," MEDIA_URL is \u002Fmedia\u002F",[66,48630,48632,48634],{"className":48631},[48617],[48619,48633],{"disabled":309,"type":48621}," Files exist in media folder",[66,48636,48638,48640],{"className":48637},[48617],[48619,48639],{"disabled":309,"type":48621}," Nginx config includes \u002Fmedia\u002F",[66,48642,48644,48646],{"className":48643},[48617],[48619,48645],{"disabled":309,"type":48621}," Permissions are correct",[66,48648,48650,48652],{"className":48649},[48617],[48619,48651],{"disabled":309,"type":48621}," Nginx restarted",[23099,48654],{},[48656,48657,48660],"article-callout",{"title":48658,"type":48659},"Still fixing the file-serving layer?","info",[16,48661,48662,48663,48667,48668,48672],{},"Verify your ",[1395,48664,48666],{"href":48665},"\u002Ffix-issues\u002Fdjango-static-files-not-loading-in-production","static files setup"," and compare it against the full ",[1395,48669,48671],{"href":48670},"\u002Fdeploy\u002Fdeploy-django-with-nginx-gunicorn-step-by-step","deploy guide"," so both Nginx mappings stay aligned.",[23099,48674],{},[11,48676,48678],{"id":48677},"related-guides","🔗 Related Guides",[63,48680,48681,48686,48691],{},[66,48682,48683],{},[1395,48684,48685],{"href":48665},"Django static files not loading",[66,48687,48688],{},[1395,48689,48690],{"href":48670},"Deploy Django with Nginx + Gunicorn",[66,48692,48693],{},[1395,48694,48696],{"href":48695},"\u002Ffix-issues\u002Ffix-django-502-bad-gateway-step-by-step-guide","Fix Django 502 Bad Gateway",[23099,48698],{},[11,48700,48701],{"id":1419},"❓ FAQ",[52,48703,48705],{"id":48704},"whats-the-difference-between-static-and-media-files","What’s the difference between static and media files?",[63,48707,48708,48711],{},[66,48709,48710],{},"Static: CSS, JS (part of your code)",[66,48712,48713],{},"Media: user uploads (dynamic content)",[52,48715,48717],{"id":48716},"can-django-serve-media-files","Can Django serve media files?",[16,48719,48720],{},"Only in development:",[106,48722,48724],{"className":2369,"code":48723,"language":1114,"meta":111,"style":111},"DEBUG = True\n",[20,48725,48726],{"__ignoreMap":111},[115,48727,48728,48730,48732],{"class":117,"line":118},[115,48729,7350],{"class":202},[115,48731,2380],{"class":121},[115,48733,2412],{"class":202},[16,48735,48145],{},[16,48737,48738],{},"👉 Use Nginx or a CDN to serve media files for better performance and security.",[52,48740,48742],{"id":48741},"why-do-uploads-work-but-files-dont-load","Why do uploads work but files don’t load?",[16,48744,48745],{},"Because:",[63,48747,48748,48751],{},[66,48749,48750],{},"Files are saved",[66,48752,48753],{},"But not served by Nginx",[11,48755,48757],{"id":48756},"final-takeaway","🎯 Final takeaway",[16,48759,48760],{},"If media files are broken:",[16,48762,48763],{},"It’s almost always:",[63,48765,48766,48768,48771],{},[66,48767,26065],{},[66,48769,48770],{},"File permissions",[66,48772,48773],{},"MEDIA_ROOT mismatch",[16,48775,48776],{},"Fix those, and uploads will work correctly.",[23099,48778],{},[16,48780,48781],{},"If you deploy often…",[16,48783,48784],{},"A repeatable, pre-tested setup will save hours of debugging.",[1485,48786,48787],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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}",{"title":111,"searchDepth":149,"depth":149,"links":48789},[48790,48791,48792,48801,48808,48813,48814,48815,48820],{"id":48091,"depth":136,"text":48092},{"id":48141,"depth":136,"text":48142},{"id":48167,"depth":136,"text":48168,"children":48793},[48794,48796,48797,48798,48799,48800],{"id":48174,"depth":149,"text":48795},"1. Check MEDIA_ROOT setting",{"id":48211,"depth":149,"text":48212},{"id":48246,"depth":149,"text":48247},{"id":48281,"depth":149,"text":48282},{"id":48346,"depth":149,"text":48347},{"id":48379,"depth":149,"text":48380},{"id":48419,"depth":136,"text":48420,"children":48802},[48803,48804,48805,48806,48807],{"id":48425,"depth":149,"text":48426},{"id":48445,"depth":149,"text":48446},{"id":48473,"depth":149,"text":48474},{"id":48496,"depth":149,"text":48497},{"id":48506,"depth":149,"text":48507},{"id":48525,"depth":136,"text":48526,"children":48809},[48810,48811,48812],{"id":48529,"depth":149,"text":48530},{"id":48559,"depth":149,"text":48560},{"id":48580,"depth":149,"text":48581},{"id":48608,"depth":136,"text":48609},{"id":48677,"depth":136,"text":48678},{"id":1419,"depth":136,"text":48701,"children":48816},[48817,48818,48819],{"id":48704,"depth":149,"text":48705},{"id":48716,"depth":149,"text":48717},{"id":48741,"depth":149,"text":48742},{"id":48756,"depth":136,"text":48757},"Troubleshooting guide to fix Django media files not serving in production. Learn how to configure media root, Nginx mappings, and permissions to resolve upload issues.",null,{},"\u002Fdjango-media-files-not-serving-uploads-broken",[48670,48826,48665],"\u002Fproduction-checklist\u002Fdjango-production-checklist-everything-you-must-do",{"title":48064,"description":48821},[],"django-media-files-not-serving-uploads-broken",[1557,48831,27252],"media-files","UqEUBRTOs1o0qJaNP7niB9Z2CPmuWdDpiDa_fZXn6o0",{"id":48834,"title":48835,"body":48836,"category":49622,"description":49623,"difficulty":1543,"extension":1544,"funnel_stage":48822,"intent":4546,"meta":49624,"navigation":309,"path":49625,"priority":3094,"related":49626,"role":4552,"section":4553,"seo":49627,"stack":49628,"stem":49629,"tags":49630,"type":1561,"__hash__":49632},"articles\u002Fgunicorn-socket-permission-denied-fix-guide.md","Fix Gunicorn Socket Permission Denied (Nginx + Django)",{"type":8,"value":48837,"toc":49591},[48838,48841,48856,48859,48865,48867,48869,48871,48918,48921,48923,48925,48929,48932,48944,48947,48952,48954,48956,48958,48962,48976,48978,48987,48990,48995,48997,49001,49015,49018,49027,49030,49035,49037,49041,49053,49056,49077,49080,49082,49086,49100,49102,49111,49114,49117,49119,49123,49137,49140,49142,49146,49158,49160,49162,49174,49177,49179,49181,49183,49187,49189,49205,49207,49223,49225,49229,49231,49248,49250,49274,49276,49280,49282,49297,49299,49303,49305,49321,49323,49327,49329,49346,49349,49358,49361,49363,49365,49367,49369,49383,49385,49394,49396,49400,49413,49415,49418,49429,49431,49433,49472,49474,49489,49491,49493,49508,49510,49512,49514,49518,49520,49527,49529,49533,49541,49543,49547,49549,49554,49556,49558,49561,49568,49571,49582,49584,49586,49589],[16,48839,48840],{},"If you're seeing errors like:",[63,48842,48843,48848,48853],{},[66,48844,48845],{},[20,48846,48847],{},"connect() to unix:\u002F...\u002Fgunicorn.sock failed (13: Permission denied)",[66,48849,48850,48851],{},"Nginx returns ",[1226,48852,13161],{},[66,48854,48855],{},"Gunicorn appears to be running, but requests fail",[16,48857,48858],{},"👉 Then you have a socket permission issue",[16,48860,48861,48862,211],{},"This guide shows you exactly how to ",[1226,48863,48864],{},"diagnose and fix it step-by-step",[23099,48866],{},[11,48868,48092],{"id":48091},[16,48870,33361],{},[106,48872,48874],{"className":108,"code":48873,"language":110,"meta":111,"style":111},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyproject\nsudo chmod 755 \u002Fvar\u002Fwww\u002Fmyproject\nsudo systemctl restart gunicorn\nsudo systemctl restart nginx\n",[20,48875,48876,48888,48898,48908],{"__ignoreMap":111},[115,48877,48878,48880,48882,48884,48886],{"class":117,"line":118},[115,48879,2001],{"class":262},[115,48881,6733],{"class":132},[115,48883,6736],{"class":202},[115,48885,6739],{"class":132},[115,48887,48398],{"class":132},[115,48889,48890,48892,48894,48896],{"class":117,"line":136},[115,48891,2001],{"class":262},[115,48893,12480],{"class":132},[115,48895,48409],{"class":202},[115,48897,48398],{"class":132},[115,48899,48900,48902,48904,48906],{"class":117,"line":149},[115,48901,2001],{"class":262},[115,48903,3480],{"class":132},[115,48905,3483],{"class":132},[115,48907,1987],{"class":132},[115,48909,48910,48912,48914,48916],{"class":117,"line":162},[115,48911,2001],{"class":262},[115,48913,3480],{"class":132},[115,48915,3483],{"class":132},[115,48917,1996],{"class":132},[16,48919,48920],{},"Then reload your site.",[16,48922,48136],{},[23099,48924],{},[11,48926,48928],{"id":48927},"what-this-error-means","🧠 What This Error Means",[16,48930,48931],{},"In this setup:",[63,48933,48934,48939],{},[66,48935,48936,48938],{},[1226,48937,1946],{}," creates a socket file",[66,48940,48941,48943],{},[1226,48942,1647],{}," connects to it",[16,48945,48946],{},"Permission denied means:",[48158,48948,48949],{},[16,48950,48951],{},"Nginx does not have permission to access the socket file",[23099,48953],{},[11,48955,48168],{"id":48167},[23099,48957],{},[52,48959,48961],{"id":48960},"_1-locate-the-socket-file","1. Locate the socket file",[106,48963,48965],{"className":108,"code":48964,"language":110,"meta":111,"style":111},"ls -l \u002Fvar\u002Fwww\u002Fmyproject\u002F\n",[20,48966,48967],{"__ignoreMap":111},[115,48968,48969,48971,48973],{"class":117,"line":118},[115,48970,532],{"class":262},[115,48972,14881],{"class":202},[115,48974,48975],{"class":132}," \u002Fvar\u002Fwww\u002Fmyproject\u002F\n",[16,48977,31301],{},[106,48979,48981],{"className":48125,"code":48980,"language":48127,"meta":111,"style":111},"srwxrwx--- 1 www-data www-data gunicorn.sock\n",[20,48982,48983],{"__ignoreMap":111},[115,48984,48985],{"class":117,"line":118},[115,48986,48980],{},[16,48988,48989],{},"👉 If missing:",[63,48991,48992],{},[66,48993,48994],{},"Gunicorn is not creating the socket",[23099,48996],{},[52,48998,49000],{"id":48999},"_2-check-ownership","2. Check ownership",[106,49002,49004],{"className":108,"code":49003,"language":110,"meta":111,"style":111},"ls -l \u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock\n",[20,49005,49006],{"__ignoreMap":111},[115,49007,49008,49010,49012],{"class":117,"line":118},[115,49009,532],{"class":262},[115,49011,14881],{"class":202},[115,49013,49014],{"class":132}," \u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock\n",[16,49016,49017],{},"Expected:",[106,49019,49021],{"className":48125,"code":49020,"language":48127,"meta":111,"style":111},"www-data www-data\n",[20,49022,49023],{"__ignoreMap":111},[115,49024,49025],{"class":117,"line":118},[115,49026,49020],{},[16,49028,49029],{},"👉 If different:",[63,49031,49032],{},[66,49033,49034],{},"Nginx cannot access it",[23099,49036],{},[52,49038,49040],{"id":49039},"_3-check-gunicorn-service-config","3. Check Gunicorn service config",[106,49042,49043],{"className":108,"code":23642,"language":110,"meta":111,"style":111},[20,49044,49045],{"__ignoreMap":111},[115,49046,49047,49049,49051],{"class":117,"line":118},[115,49048,2001],{"class":262},[115,49050,12408],{"class":132},[115,49052,23653],{"class":132},[16,49054,49055],{},"Ensure:",[106,49057,49059],{"className":2026,"code":49058,"language":2028,"meta":111,"style":111},"[Service]\nUser=www-data\nGroup=www-data\n",[20,49060,49061,49065,49071],{"__ignoreMap":111},[115,49062,49063],{"class":117,"line":118},[115,49064,2060],{"class":262},[115,49066,49067,49069],{"class":117,"line":136},[115,49068,2065],{"class":121},[115,49070,2076],{"class":125},[115,49072,49073,49075],{"class":117,"line":149},[115,49074,2073],{"class":121},[115,49076,2076],{"class":125},[16,49078,49079],{},"👉 This ensures Gunicorn creates the socket with the correct permissions",[23099,49081],{},[52,49083,49085],{"id":49084},"_4-check-directory-permissions","4. Check directory permissions",[106,49087,49089],{"className":108,"code":49088,"language":110,"meta":111,"style":111},"ls -ld \u002Fvar\u002Fwww\u002Fmyproject\n",[20,49090,49091],{"__ignoreMap":111},[115,49092,49093,49095,49098],{"class":117,"line":118},[115,49094,532],{"class":262},[115,49096,49097],{"class":202}," -ld",[115,49099,48398],{"class":132},[16,49101,49017],{},[106,49103,49105],{"className":48125,"code":49104,"language":48127,"meta":111,"style":111},"drwxr-xr-x www-data www-data\n",[20,49106,49107],{"__ignoreMap":111},[115,49108,49109],{"class":117,"line":118},[115,49110,49104],{},[16,49112,49113],{},"👉 If too restrictive:",[16,49115,49116],{},"Nginx cannot traverse directory to access socket",[23099,49118],{},[52,49120,49122],{"id":49121},"_5-restart-gunicorn","5. Restart Gunicorn",[106,49124,49125],{"className":108,"code":3471,"language":110,"meta":111,"style":111},[20,49126,49127],{"__ignoreMap":111},[115,49128,49129,49131,49133,49135],{"class":117,"line":118},[115,49130,2001],{"class":262},[115,49132,3480],{"class":132},[115,49134,3483],{"class":132},[115,49136,1987],{"class":132},[16,49138,49139],{},"Then check socket again.",[23099,49141],{},[52,49143,49145],{"id":49144},"_6-check-nginx-config","6. Check Nginx config",[106,49147,49148],{"className":108,"code":48288,"language":110,"meta":111,"style":111},[20,49149,49150],{"__ignoreMap":111},[115,49151,49152,49154,49156],{"class":117,"line":118},[115,49153,2001],{"class":262},[115,49155,12408],{"class":132},[115,49157,48299],{"class":132},[16,49159,48302],{},[16,49161,49055],{},[106,49163,49165],{"className":2154,"code":49164,"language":2156,"meta":111,"style":111},"proxy_pass http:\u002F\u002Funix:\u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock;\n",[20,49166,49167],{"__ignoreMap":111},[115,49168,49169,49171],{"class":117,"line":118},[115,49170,12989],{"class":121},[115,49172,49173],{"class":125},"http:\u002F\u002Funix:\u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock;\n",[16,49175,49176],{},"👉 Path must match exactly",[23099,49178],{},[11,49180,48420],{"id":48419},[23099,49182],{},[52,49184,49186],{"id":49185},"wrong-file-ownership","🔴 Wrong file ownership",[16,49188,48429],{},[106,49190,49191],{"className":108,"code":48479,"language":110,"meta":111,"style":111},[20,49192,49193],{"__ignoreMap":111},[115,49194,49195,49197,49199,49201,49203],{"class":117,"line":118},[115,49196,2001],{"class":262},[115,49198,6733],{"class":132},[115,49200,6736],{"class":202},[115,49202,6739],{"class":132},[115,49204,48398],{"class":132},[23099,49206],{},[106,49208,49209],{"className":108,"code":48479,"language":110,"meta":111,"style":111},[20,49210,49211],{"__ignoreMap":111},[115,49212,49213,49215,49217,49219,49221],{"class":117,"line":118},[115,49214,2001],{"class":262},[115,49216,6733],{"class":132},[115,49218,6736],{"class":202},[115,49220,6739],{"class":132},[115,49222,48398],{"class":132},[23099,49224],{},[52,49226,49228],{"id":49227},"gunicorn-running-as-wrong-user","🔴 Gunicorn running as wrong user",[16,49230,48429],{},[106,49232,49234],{"className":2026,"code":49233,"language":2028,"meta":111,"style":111},"User=www-data\nGroup=www-data\n",[20,49235,49236,49242],{"__ignoreMap":111},[115,49237,49238,49240],{"class":117,"line":118},[115,49239,2065],{"class":121},[115,49241,2076],{"class":125},[115,49243,49244,49246],{"class":117,"line":136},[115,49245,2073],{"class":121},[115,49247,2076],{"class":125},[16,49249,5144],{},[106,49251,49253],{"className":108,"code":49252,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reexec\nsudo systemctl restart gunicorn\n",[20,49254,49255,49264],{"__ignoreMap":111},[115,49256,49257,49259,49261],{"class":117,"line":118},[115,49258,2001],{"class":262},[115,49260,3480],{"class":132},[115,49262,49263],{"class":132}," daemon-reexec\n",[115,49265,49266,49268,49270,49272],{"class":117,"line":136},[115,49267,2001],{"class":262},[115,49269,3480],{"class":132},[115,49271,3483],{"class":132},[115,49273,1987],{"class":132},[23099,49275],{},[52,49277,49279],{"id":49278},"directory-permissions-too-strict","🔴 Directory permissions too strict",[16,49281,48429],{},[106,49283,49285],{"className":108,"code":49284,"language":110,"meta":111,"style":111},"sudo chmod 755 \u002Fvar\u002Fwww\u002Fmyproject\n",[20,49286,49287],{"__ignoreMap":111},[115,49288,49289,49291,49293,49295],{"class":117,"line":118},[115,49290,2001],{"class":262},[115,49292,12480],{"class":132},[115,49294,48409],{"class":202},[115,49296,48398],{"class":132},[23099,49298],{},[52,49300,49302],{"id":49301},"socket-permissions-incorrect","🔴 Socket permissions incorrect",[16,49304,48429],{},[106,49306,49308],{"className":108,"code":49307,"language":110,"meta":111,"style":111},"sudo chmod 660 \u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock\n",[20,49309,49310],{"__ignoreMap":111},[115,49311,49312,49314,49316,49319],{"class":117,"line":118},[115,49313,2001],{"class":262},[115,49315,12480],{"class":132},[115,49317,49318],{"class":202}," 660",[115,49320,49014],{"class":132},[23099,49322],{},[52,49324,49326],{"id":49325},"nginx-running-as-different-user","🔴 Nginx running as different user",[16,49328,5438],{},[106,49330,49332],{"className":108,"code":49331,"language":110,"meta":111,"style":111},"ps aux | grep nginx\n",[20,49333,49334],{"__ignoreMap":111},[115,49335,49336,49338,49340,49342,49344],{"class":117,"line":118},[115,49337,4830],{"class":262},[115,49339,4833],{"class":132},[115,49341,579],{"class":121},[115,49343,4838],{"class":262},[115,49345,1996],{"class":132},[16,49347,49348],{},"Usually:",[106,49350,49352],{"className":48125,"code":49351,"language":48127,"meta":111,"style":111},"www-data\n",[20,49353,49354],{"__ignoreMap":111},[115,49355,49356],{"class":117,"line":118},[115,49357,49351],{},[16,49359,49360],{},"👉 Must match Gunicorn group",[23099,49362],{},[11,49364,48526],{"id":48525},[23099,49366],{},[52,49368,48530],{"id":48529},[106,49370,49371],{"className":108,"code":48533,"language":110,"meta":111,"style":111},[20,49372,49373],{"__ignoreMap":111},[115,49374,49375,49377,49379,49381],{"class":117,"line":118},[115,49376,2001],{"class":262},[115,49378,13188],{"class":132},[115,49380,2777],{"class":202},[115,49382,13195],{"class":132},[16,49384,31301],{},[63,49386,49387,49391],{},[66,49388,49389],{},[20,49390,48556],{},[66,49392,49393],{},"socket errors",[23099,49395],{},[52,49397,49399],{"id":49398},"check-gunicorn-logs","Check Gunicorn logs",[106,49401,49403],{"className":108,"code":49402,"language":110,"meta":111,"style":111},"journalctl -u gunicorn\n",[20,49404,49405],{"__ignoreMap":111},[115,49406,49407,49409,49411],{"class":117,"line":118},[115,49408,2785],{"class":262},[115,49410,2788],{"class":202},[115,49412,1987],{"class":132},[23099,49414],{},[16,49416,49417],{},"Check socket permissions directly",[106,49419,49421],{"className":108,"code":49420,"language":110,"meta":111,"style":111},"stat \u002Fvar\u002Fwww\u002Fmyproject\u002Fgunicorn.sock\n",[20,49422,49423],{"__ignoreMap":111},[115,49424,49425,49427],{"class":117,"line":118},[115,49426,31627],{"class":202},[115,49428,49014],{"class":132},[23099,49430],{},[11,49432,48609],{"id":48608},[63,49434,49436,49442,49448,49454,49460,49466],{"className":49435},[48613],[66,49437,49439,49441],{"className":49438},[48617],[48619,49440],{"disabled":309,"type":48621}," Socket file exists",[66,49443,49445,49447],{"className":49444},[48617],[48619,49446],{"disabled":309,"type":48621}," Owned by www-data",[66,49449,49451,49453],{"className":49450},[48617],[48619,49452],{"disabled":309,"type":48621}," Gunicorn runs as www-data",[66,49455,49457,49459],{"className":49456},[48617],[48619,49458],{"disabled":309,"type":48621}," Directory permissions allow access",[66,49461,49463,49465],{"className":49462},[48617],[48619,49464],{"disabled":309,"type":48621}," Nginx uses correct socket path",[66,49467,49469,49471],{"className":49468},[48617],[48619,49470],{"disabled":309,"type":48621}," Services restarted",[23099,49473],{},[48656,49475,49478],{"title":49476,"type":49477},"After fixing permissions, confirm the full Nginx ↔ Gunicorn chain","warning",[16,49479,49480,49481,49485,49486,211],{},"If requests still fail, walk through the focused ",[1395,49482,49484],{"href":49483},"\u002Ffix-issues\u002Ffix-nginx-not-connecting-to-gunicorn-connection-refused","connection refused guide"," and compare your service layout with the main ",[1395,49487,49488],{"href":48670},"deploy tutorial",[23099,49490],{},[11,49492,48678],{"id":48677},[63,49494,49495,49499,49504],{},[66,49496,49497],{},[1395,49498,48696],{"href":48695},[66,49500,49501],{},[1395,49502,49503],{"href":49483},"Nginx not connecting to Gunicorn",[66,49505,49506],{},[1395,49507,48690],{"href":48670},[23099,49509],{},[11,49511,48701],{"id":1419},[23099,49513],{},[52,49515,49517],{"id":49516},"why-does-this-error-happen","Why does this error happen?",[16,49519,48745],{},[63,49521,49522,49525],{},[66,49523,49524],{},"Gunicorn creates the socket",[66,49526,49034],{},[23099,49528],{},[52,49530,49532],{"id":49531},"should-i-use-sockets-or-ports","Should I use sockets or ports?",[63,49534,49535,49538],{},[66,49536,49537],{},"Sockets → faster, more efficient",[66,49539,49540],{},"Ports → easier debugging",[23099,49542],{},[52,49544,49546],{"id":49545},"why-does-restarting-fix-it-sometimes","Why does restarting fix it sometimes?",[16,49548,48745],{},[63,49550,49551],{},[66,49552,49553],{},"Socket gets recreated with correct permissions",[23099,49555],{},[11,49557,48757],{"id":48756},[16,49559,49560],{},"“Permission denied” means:",[48158,49562,49563],{},[16,49564,49565],{},[1226,49566,49567],{},"Nginx cannot access Gunicorn’s socket",[16,49569,49570],{},"Fix it by ensuring:",[63,49572,49573,49576,49579],{},[66,49574,49575],{},"Correct ownership",[66,49577,49578],{},"Correct permissions",[66,49580,49581],{},"Matching configuration",[23099,49583],{},[16,49585,48781],{},[16,49587,49588],{},"A repeatable, tested setup will prevent this issue entirely.",[1485,49590,30654],{},{"title":111,"searchDepth":149,"depth":149,"links":49592},[49593,49594,49595,49603,49610,49614,49615,49616,49621],{"id":48091,"depth":136,"text":48092},{"id":48927,"depth":136,"text":48928},{"id":48167,"depth":136,"text":48168,"children":49596},[49597,49598,49599,49600,49601,49602],{"id":48960,"depth":149,"text":48961},{"id":48999,"depth":149,"text":49000},{"id":49039,"depth":149,"text":49040},{"id":49084,"depth":149,"text":49085},{"id":49121,"depth":149,"text":49122},{"id":49144,"depth":149,"text":49145},{"id":48419,"depth":136,"text":48420,"children":49604},[49605,49606,49607,49608,49609],{"id":49185,"depth":149,"text":49186},{"id":49227,"depth":149,"text":49228},{"id":49278,"depth":149,"text":49279},{"id":49301,"depth":149,"text":49302},{"id":49325,"depth":149,"text":49326},{"id":48525,"depth":136,"text":48526,"children":49611},[49612,49613],{"id":48529,"depth":149,"text":48530},{"id":49398,"depth":149,"text":49399},{"id":48608,"depth":136,"text":48609},{"id":48677,"depth":136,"text":48678},{"id":1419,"depth":136,"text":48701,"children":49617},[49618,49619,49620],{"id":49516,"depth":149,"text":49517},{"id":49531,"depth":149,"text":49532},{"id":49545,"depth":149,"text":49546},{"id":48756,"depth":136,"text":48757},"Permissions","Learn how to resolve 'Permission Denied' errors when Nginx tries to access Gunicorn's socket file. This guide covers common causes and step-by-step fixes to ensure smooth communication between Nginx and Gunicorn in your Django deployment.",{},"\u002Fgunicorn-socket-permission-denied-fix-guide",[48695,49483,48670],{"title":48835,"description":49623},[],"gunicorn-socket-permission-denied-fix-guide",[14954,2156,49631],"linux","d2-H2WiCSbm7brQVicq_u5RweEXWKHlJkTGYBD58bZw",{"id":49634,"title":49635,"body":49636,"category":4543,"description":50895,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":50896,"navigation":309,"path":50897,"priority":50898,"related":50899,"role":4552,"section":4553,"seo":50900,"stack":50901,"stem":50902,"tags":50903,"type":4558,"__hash__":50904},"articles\u002Ffix-nginx-413-django-uploads.md","Fix Nginx 413 Request Entity Too Large for Django Uploads",{"type":8,"value":49637,"toc":50861},[49638,49640,49651,49656,49659,49673,49679,49681,49688,49713,49718,49720,49724,49727,49730,49744,49747,49764,49767,49773,49776,49791,49794,49805,49809,49826,49830,49833,49836,49850,49853,49875,49889,49902,49912,49916,49933,49939,49942,49959,49963,49966,50101,50105,50112,50260,50263,50267,50280,50284,50287,50299,50302,50317,50320,50355,50358,50362,50381,50385,50388,50392,50395,50426,50432,50434,50445,50449,50452,50463,50466,50481,50485,50488,50502,50506,50509,50512,50525,50528,50532,50546,50550,50553,50564,50570,50603,50606,50635,50638,50641,50643,50648,50659,50666,50677,50680,50694,50698,50704,50706,50758,50760,50785,50787,50793,50799,50803,50806,50810,50813,50817,50829,50833,50835,50859],[11,49639,14],{"id":13},[16,49641,49642,49643,49646,49647,49650],{},"If a Django file upload fails with ",[1226,49644,49645],{},"413 Request Entity Too Large",", the rejection usually happens ",[1226,49648,49649],{},"before Django sees the request",". In a production stack like:",[16,49652,49653],{},[1226,49654,49655],{},"browser → Nginx → Gunicorn\u002FUvicorn → Django",[16,49657,49658],{},"Nginx often enforces the first upload size limit. That means:",[63,49660,49661,49664,49667,49670],{},[66,49662,49663],{},"small files succeed",[66,49665,49666],{},"larger files fail immediately",[66,49668,49669],{},"Django logs show nothing for the failed request",[66,49671,49672],{},"Nginx error logs show request body rejection",[16,49674,49675,49676,49678],{},"This is a common production issue when upload requirements change but the reverse proxy limit stays at its default or an older value. The fix is usually to set ",[20,49677,13316],{}," correctly in Nginx, reload safely, and then verify the rest of the stack can also handle the intended file size.",[11,49680,30],{"id":29},[16,49682,49683,49684,49687],{},"To fix ",[1226,49685,49686],{},"Nginx 413 Request Entity Too Large"," for Django uploads, do this:",[1173,49689,49690,49693,49699,49704,49707,49710],{},[66,49691,49692],{},"Confirm Nginx is returning the 413.",[66,49694,49695,49696,49698],{},"Set ",[20,49697,13316],{}," in the active Nginx config for the affected site or upload endpoint.",[66,49700,49701,49702,211],{},"Validate with ",[20,49703,7611],{},[66,49705,49706],{},"Reload Nginx with no connection drop.",[66,49708,49709],{},"Test uploads below, near, and above the configured limit.",[66,49711,49712],{},"Check Django, app server, container, ingress, and disk space limits so the full path supports the file size safely.",[16,49714,7471,49715,49717],{},[1226,49716,7474],{}," remove upload limits entirely unless you have a specific reason and compensating controls.",[11,49719,43],{"id":42},[52,49721,49723],{"id":49722},"_1-confirm-that-nginx-is-the-component-returning-413","1. Confirm that Nginx is the component returning 413",[16,49725,49726],{},"Check the browser response first. If the response page looks like a plain Nginx error page, that is already a strong signal.",[16,49728,49729],{},"Then inspect logs on the server:",[106,49731,49732],{"className":108,"code":48533,"language":110,"meta":111,"style":111},[20,49733,49734],{"__ignoreMap":111},[115,49735,49736,49738,49740,49742],{"class":117,"line":118},[115,49737,2001],{"class":262},[115,49739,13188],{"class":132},[115,49741,2777],{"class":202},[115,49743,13195],{"class":132},[16,49745,49746],{},"Or if your distro logs through systemd:",[106,49748,49750],{"className":108,"code":49749,"language":110,"meta":111,"style":111},"sudo journalctl -u nginx -f\n",[20,49751,49752],{"__ignoreMap":111},[115,49753,49754,49756,49758,49760,49762],{"class":117,"line":118},[115,49755,2001],{"class":262},[115,49757,5030],{"class":132},[115,49759,2788],{"class":202},[115,49761,3906],{"class":132},[115,49763,36482],{"class":202},[16,49765,49766],{},"You may see messages like:",[106,49768,49771],{"className":49769,"code":49770,"language":247,"meta":111},[245],"client intended to send too large body: 31457280 bytes\n",[20,49772,49770],{"__ignoreMap":111},[16,49774,49775],{},"Also review access logs for the failing request:",[106,49777,49779],{"className":108,"code":49778,"language":110,"meta":111,"style":111},"sudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[20,49780,49781],{"__ignoreMap":111},[115,49782,49783,49785,49787,49789],{"class":117,"line":118},[115,49784,2001],{"class":262},[115,49786,13188],{"class":132},[115,49788,2777],{"class":202},[115,49790,30354],{"class":132},[16,49792,49793],{},"If Nginx returns the 413 directly, Django and Gunicorn often will not log the request at all.",[16,49795,49796,49797,49800,49801,49804],{},"If the server hosts multiple Django sites, confirm which virtual host answered the request. ",[20,49798,49799],{},"nginx -T"," will show the loaded ",[20,49802,49803],{},"server_name"," and matching config, which helps avoid changing the wrong site.",[16,49806,49807],{},[1226,49808,3515],{},[63,49810,49811,49814,49817,49820],{},[66,49812,49813],{},"failing upload returns HTTP 413",[66,49815,49816],{},"Nginx error log shows body-size rejection",[66,49818,49819],{},"Django app logs do not show the request reaching the application",[66,49821,49822,49823,49825],{},"you know which Nginx ",[20,49824,2163],{}," block handled the request",[52,49827,49829],{"id":49828},"_2-find-the-active-nginx-config-file","2. Find the active Nginx config file",[16,49831,49832],{},"You need to edit the file that is actually loaded for the affected site.",[16,49834,49835],{},"Inspect the full active config:",[106,49837,49839],{"className":108,"code":49838,"language":110,"meta":111,"style":111},"sudo nginx -T\n",[20,49840,49841],{"__ignoreMap":111},[115,49842,49843,49845,49847],{"class":117,"line":118},[115,49844,2001],{"class":262},[115,49846,3906],{"class":132},[115,49848,49849],{"class":202}," -T\n",[16,49851,49852],{},"List common site directories:",[106,49854,49856],{"className":108,"code":49855,"language":110,"meta":111,"style":111},"ls -lah \u002Fetc\u002Fnginx\u002Fsites-enabled\u002F\nls -lah \u002Fetc\u002Fnginx\u002Fsites-available\u002F\n",[20,49857,49858,49866],{"__ignoreMap":111},[115,49859,49860,49862,49864],{"class":117,"line":118},[115,49861,532],{"class":262},[115,49863,12216],{"class":202},[115,49865,15712],{"class":132},[115,49867,49868,49870,49872],{"class":117,"line":136},[115,49869,532],{"class":262},[115,49871,12216],{"class":202},[115,49873,49874],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002F\n",[16,49876,49877,49878,49881,49882,49885,49886,211],{},"Typical Ubuntu-style setups load a site file from ",[20,49879,49880],{},"sites-enabled"," via symlink. In other layouts, the server block may be in ",[20,49883,49884],{},"conf.d\u002F*.conf"," or directly inside ",[20,49887,49888],{},"nginx.conf",[16,49890,49891,49893,49894,1153,49897,47917,49899,49901],{},[20,49892,13316],{}," is commonly configured at the ",[20,49895,49896],{},"http",[20,49898,2163],{},[20,49900,7128],{}," level. Use the narrowest scope that matches your requirement. Avoid defining conflicting values in multiple places unless you are doing it deliberately.",[16,49903,49904,49905,49907,49908,49911],{},"If your site serves uploads over HTTPS, make sure you update the active ",[20,49906,2163],{}," block handling ",[20,49909,49910],{},"listen 443 ssl;"," as well, not just the port 80 redirect vhost.",[16,49913,49914],{},[1226,49915,3515],{},[63,49917,49918,49924,49930],{},[66,49919,49920,49921,49923],{},"you know which ",[20,49922,2163],{}," block handles the domain",[66,49925,49926,49927,49929],{},"you know whether a more specific ",[20,49928,7128],{}," block overrides a broader setting",[66,49931,49932],{},"you have identified the active HTTP or HTTPS vhost that receives uploads",[52,49934,49936,49937],{"id":49935},"_3-apply-the-nginx-413-django-fix-with-client_max_body_size","3. Apply the Nginx 413 Django fix with ",[20,49938,13316],{},[16,49940,49941],{},"Before editing, back up the current config:",[106,49943,49945],{"className":108,"code":49944,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf.bak\n",[20,49946,49947],{"__ignoreMap":111},[115,49948,49949,49951,49953,49956],{"class":117,"line":118},[115,49950,2001],{"class":262},[115,49952,6516],{"class":132},[115,49954,49955],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf",[115,49957,49958],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf.bak\n",[1850,49960,49962],{"id":49961},"option-a-set-the-limit-for-one-django-site","Option A: set the limit for one Django site",[16,49964,49965],{},"Example server block:",[106,49967,49969],{"className":2154,"code":49968,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    client_max_body_size 25M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fapp\u002Fcurrent\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fapp\u002Fcurrent\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock:;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,49970,49971,49977,49985,49991,49995,50004,50008,50016,50023,50027,50031,50039,50046,50050,50054,50062,50069,50075,50081,50087,50093,50097],{"__ignoreMap":111},[115,49972,49973,49975],{"class":117,"line":118},[115,49974,2163],{"class":121},[115,49976,2166],{"class":125},[115,49978,49979,49981,49983],{"class":117,"line":136},[115,49980,2171],{"class":121},[115,49982,3808],{"class":202},[115,49984,3811],{"class":125},[115,49986,49987,49989],{"class":117,"line":149},[115,49988,2182],{"class":121},[115,49990,3713],{"class":125},[115,49992,49993],{"class":117,"line":162},[115,49994,310],{"emptyLinePlaceholder":309},[115,49996,49997,49999,50002],{"class":117,"line":175},[115,49998,6987],{"class":121},[115,50000,50001],{"class":202},"25M",[115,50003,3811],{"class":125},[115,50005,50006],{"class":117,"line":350},[115,50007,310],{"emptyLinePlaceholder":309},[115,50009,50010,50012,50014],{"class":117,"line":365},[115,50011,2214],{"class":121},[115,50013,2217],{"class":262},[115,50015,2220],{"class":125},[115,50017,50018,50020],{"class":117,"line":380},[115,50019,2225],{"class":121},[115,50021,50022],{"class":125},"\u002Fsrv\u002Fapp\u002Fcurrent\u002Fstaticfiles\u002F;\n",[115,50024,50025],{"class":117,"line":487},[115,50026,2233],{"class":125},[115,50028,50029],{"class":117,"line":2095},[115,50030,310],{"emptyLinePlaceholder":309},[115,50032,50033,50035,50037],{"class":117,"line":2104},[115,50034,2214],{"class":121},[115,50036,2244],{"class":262},[115,50038,2220],{"class":125},[115,50040,50041,50043],{"class":117,"line":2113},[115,50042,2225],{"class":121},[115,50044,50045],{"class":125},"\u002Fsrv\u002Fapp\u002Fcurrent\u002Fmedia\u002F;\n",[115,50047,50048],{"class":117,"line":2122},[115,50049,2233],{"class":125},[115,50051,50052],{"class":117,"line":2131},[115,50053,310],{"emptyLinePlaceholder":309},[115,50055,50056,50058,50060],{"class":117,"line":2136},[115,50057,2214],{"class":121},[115,50059,2268],{"class":262},[115,50061,2220],{"class":125},[115,50063,50064,50066],{"class":117,"line":2142},[115,50065,2276],{"class":121},[115,50067,50068],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock:;\n",[115,50070,50071,50073],{"class":117,"line":2273},[115,50072,2285],{"class":121},[115,50074,2288],{"class":125},[115,50076,50077,50079],{"class":117,"line":2282},[115,50078,2285],{"class":121},[115,50080,3767],{"class":125},[115,50082,50083,50085],{"class":117,"line":2291},[115,50084,2285],{"class":121},[115,50086,2312],{"class":125},[115,50088,50089,50091],{"class":117,"line":2299},[115,50090,2285],{"class":121},[115,50092,2304],{"class":125},[115,50094,50095],{"class":117,"line":2307},[115,50096,2233],{"class":125},[115,50098,50099],{"class":117,"line":2315},[115,50100,2323],{"class":125},[1850,50102,50104],{"id":50103},"option-b-set-a-different-limit-for-a-specific-upload-endpoint","Option B: set a different limit for a specific upload endpoint",[16,50106,50107,50108,50111],{},"If only ",[20,50109,50110],{},"\u002Fapi\u002Fuploads\u002F"," needs a larger limit, scope it there:",[106,50113,50115],{"className":2154,"code":50114,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fapi\u002Fuploads\u002F {\n        client_max_body_size 50M;\n\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock:;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location \u002F {\n        client_max_body_size 10M;\n\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock:;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,50116,50117,50123,50131,50137,50141,50150,50160,50164,50170,50176,50182,50188,50194,50198,50202,50210,50218,50222,50228,50234,50240,50246,50252,50256],{"__ignoreMap":111},[115,50118,50119,50121],{"class":117,"line":118},[115,50120,2163],{"class":121},[115,50122,2166],{"class":125},[115,50124,50125,50127,50129],{"class":117,"line":136},[115,50126,2171],{"class":121},[115,50128,3808],{"class":202},[115,50130,3811],{"class":125},[115,50132,50133,50135],{"class":117,"line":149},[115,50134,2182],{"class":121},[115,50136,2185],{"class":125},[115,50138,50139],{"class":117,"line":162},[115,50140,310],{"emptyLinePlaceholder":309},[115,50142,50143,50145,50148],{"class":117,"line":175},[115,50144,2214],{"class":121},[115,50146,50147],{"class":262}," \u002Fapi\u002Fuploads\u002F ",[115,50149,2220],{"class":125},[115,50151,50152,50155,50158],{"class":117,"line":350},[115,50153,50154],{"class":121},"        client_max_body_size ",[115,50156,50157],{"class":202},"50M",[115,50159,3811],{"class":125},[115,50161,50162],{"class":117,"line":365},[115,50163,310],{"emptyLinePlaceholder":309},[115,50165,50166,50168],{"class":117,"line":380},[115,50167,2276],{"class":121},[115,50169,50068],{"class":125},[115,50171,50172,50174],{"class":117,"line":487},[115,50173,2285],{"class":121},[115,50175,2288],{"class":125},[115,50177,50178,50180],{"class":117,"line":2095},[115,50179,2285],{"class":121},[115,50181,3767],{"class":125},[115,50183,50184,50186],{"class":117,"line":2104},[115,50185,2285],{"class":121},[115,50187,2312],{"class":125},[115,50189,50190,50192],{"class":117,"line":2113},[115,50191,2285],{"class":121},[115,50193,2304],{"class":125},[115,50195,50196],{"class":117,"line":2122},[115,50197,2233],{"class":125},[115,50199,50200],{"class":117,"line":2131},[115,50201,310],{"emptyLinePlaceholder":309},[115,50203,50204,50206,50208],{"class":117,"line":2136},[115,50205,2214],{"class":121},[115,50207,2268],{"class":262},[115,50209,2220],{"class":125},[115,50211,50212,50214,50216],{"class":117,"line":2142},[115,50213,50154],{"class":121},[115,50215,12827],{"class":202},[115,50217,3811],{"class":125},[115,50219,50220],{"class":117,"line":2273},[115,50221,310],{"emptyLinePlaceholder":309},[115,50223,50224,50226],{"class":117,"line":2282},[115,50225,2276],{"class":121},[115,50227,50068],{"class":125},[115,50229,50230,50232],{"class":117,"line":2291},[115,50231,2285],{"class":121},[115,50233,2288],{"class":125},[115,50235,50236,50238],{"class":117,"line":2299},[115,50237,2285],{"class":121},[115,50239,3767],{"class":125},[115,50241,50242,50244],{"class":117,"line":2307},[115,50243,2285],{"class":121},[115,50245,2312],{"class":125},[115,50247,50248,50250],{"class":117,"line":2315},[115,50249,2285],{"class":121},[115,50251,2304],{"class":125},[115,50253,50254],{"class":117,"line":2320},[115,50255,2233],{"class":125},[115,50257,50258],{"class":117,"line":7083},[115,50259,2323],{"class":125},[16,50261,50262],{},"Choose a value based on actual application requirements. If users upload documents up to 20 MB, set a limit slightly above that, not 500 MB by default.",[16,50264,50265],{},[1226,50266,3515],{},[63,50268,50269,50272,50275],{},[66,50270,50271],{},"configured limit matches real upload requirements",[66,50273,50274],{},"no broader or narrower block unintentionally overrides it",[66,50276,50277,50278],{},"the active TLS vhost has the same intended limit if uploads terminate on ",[20,50279,2174],{},[52,50281,50283],{"id":50282},"_4-test-and-reload-nginx-safely","4. Test and reload Nginx safely",[16,50285,50286],{},"Validate syntax before reload:",[106,50288,50289],{"className":108,"code":4271,"language":110,"meta":111,"style":111},[20,50290,50291],{"__ignoreMap":111},[115,50292,50293,50295,50297],{"class":117,"line":118},[115,50294,2001],{"class":262},[115,50296,3906],{"class":132},[115,50298,4282],{"class":202},[16,50300,50301],{},"If valid, reload without dropping active connections:",[106,50303,50305],{"className":108,"code":50304,"language":110,"meta":111,"style":111},"sudo systemctl reload nginx\n",[20,50306,50307],{"__ignoreMap":111},[115,50308,50309,50311,50313,50315],{"class":117,"line":118},[115,50310,2001],{"class":262},[115,50312,3480],{"class":132},[115,50314,3919],{"class":132},[115,50316,1996],{"class":132},[16,50318,50319],{},"If the config test fails, do not reload. Restore the backup and test again:",[106,50321,50323],{"className":108,"code":50322,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf.bak \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,50324,50325,50337,50345],{"__ignoreMap":111},[115,50326,50327,50329,50331,50334],{"class":117,"line":118},[115,50328,2001],{"class":262},[115,50330,6516],{"class":132},[115,50332,50333],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf.bak",[115,50335,50336],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fexample.conf\n",[115,50338,50339,50341,50343],{"class":117,"line":136},[115,50340,2001],{"class":262},[115,50342,3906],{"class":132},[115,50344,4282],{"class":202},[115,50346,50347,50349,50351,50353],{"class":117,"line":149},[115,50348,2001],{"class":262},[115,50350,3480],{"class":132},[115,50352,3919],{"class":132},[115,50354,1996],{"class":132},[16,50356,50357],{},"If uploads start failing in production after the change, revert to the previous known-good config, reload Nginx, and re-test with a small file. A size-limit change is low risk, but rollback should still be explicit and fast.",[16,50359,50360],{},[1226,50361,3515],{},[63,50363,50364,50369,50372,50378],{},[66,50365,50366,50368],{},[20,50367,7611],{}," reports syntax is ok and test is successful",[66,50370,50371],{},"reload completes without service failure",[66,50373,50374,50377],{},[20,50375,50376],{},"systemctl status nginx"," remains healthy",[66,50379,50380],{},"you can restore the previous config quickly if needed",[52,50382,50384],{"id":50383},"_5-check-the-full-django-upload-path","5. Check the full Django upload path",[16,50386,50387],{},"Fixing Nginx is only part of the path.",[1850,50389,50391],{"id":50390},"django-settings-that-affect-uploads","Django settings that affect uploads",[16,50393,50394],{},"Review upload-related settings if you handle larger files:",[106,50396,50398],{"className":2369,"code":50397,"language":1114,"meta":111,"style":111},"FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440   # files above this size are streamed to a temp file instead of kept entirely in memory\nDATA_UPLOAD_MAX_MEMORY_SIZE = 10485760  # limits how much non-file request data Django will read into memory\n",[20,50399,50400,50413],{"__ignoreMap":111},[115,50401,50402,50405,50407,50410],{"class":117,"line":118},[115,50403,50404],{"class":202},"FILE_UPLOAD_MAX_MEMORY_SIZE",[115,50406,2380],{"class":121},[115,50408,50409],{"class":202}," 2621440",[115,50411,50412],{"class":3861},"   # files above this size are streamed to a temp file instead of kept entirely in memory\n",[115,50414,50415,50418,50420,50423],{"class":117,"line":136},[115,50416,50417],{"class":202},"DATA_UPLOAD_MAX_MEMORY_SIZE",[115,50419,2380],{"class":121},[115,50421,50422],{"class":202}," 10485760",[115,50424,50425],{"class":3861},"  # limits how much non-file request data Django will read into memory\n",[16,50427,50428,50429,50431],{},"These do ",[1226,50430,7474],{}," replace the Nginx limit, and they are not a direct substitute for an end-to-end upload size policy. They affect how Django handles request data after the request reaches the app.",[16,50433,1132],{},[63,50435,50436,50439,50442],{},[66,50437,50438],{},"the upload form and view allow the file type and size",[66,50440,50441],{},"media storage is configured correctly",[66,50443,50444],{},"the destination filesystem or object storage is writable",[1850,50446,50448],{"id":50447},"gunicorn-or-uvicorn-considerations","Gunicorn or Uvicorn considerations",[16,50450,50451],{},"Gunicorn usually is not the component generating 413 in this setup, but larger uploads may expose:",[63,50453,50454,50457,50460],{},[66,50455,50456],{},"worker timeouts",[66,50458,50459],{},"slow request handling",[66,50461,50462],{},"temporary file pressure",[16,50464,50465],{},"Review your systemd unit or process manager config if uploads are slow or fail later:",[106,50467,50469],{"className":108,"code":50468,"language":110,"meta":111,"style":111},"sudo systemctl cat gunicorn\n",[20,50470,50471],{"__ignoreMap":111},[115,50472,50473,50475,50477,50479],{"class":117,"line":118},[115,50474,2001],{"class":262},[115,50476,3480],{"class":132},[115,50478,4973],{"class":132},[115,50480,1987],{"class":132},[1850,50482,50484],{"id":50483},"container-ingress-load-balancer-or-platform-limits","Container, ingress, load balancer, or platform limits",[16,50486,50487],{},"If you run behind another proxy layer, Nginx may no longer be the only limit. Check for:",[63,50489,50490,50493,50496,50499],{},[66,50491,50492],{},"Kubernetes ingress body-size settings",[66,50494,50495],{},"cloud load balancer request size restrictions",[66,50497,50498],{},"CDN or WAF upload limits",[66,50500,50501],{},"containerized Nginx config generated from environment variables",[1850,50503,50505],{"id":50504},"temporary-file-storage-and-disk-space","Temporary file storage and disk space",[16,50507,50508],{},"Large request bodies may be buffered to disk by Nginx before they reach Django. That means accepted uploads can still fail if temp storage is full or I\u002FO is under pressure.",[16,50510,50511],{},"Verify free space:",[106,50513,50515],{"className":108,"code":50514,"language":110,"meta":111,"style":111},"df -h\n",[20,50516,50517],{"__ignoreMap":111},[115,50518,50519,50522],{"class":117,"line":118},[115,50520,50521],{"class":262},"df",[115,50523,50524],{"class":202}," -h\n",[16,50526,50527],{},"If you use a custom temp path, confirm it exists and has capacity. Low disk space can turn a 413 fix into a different upload failure later in the request path.",[16,50529,50530],{},[1226,50531,3515],{},[63,50533,50534,50537,50540,50543],{},[66,50535,50536],{},"Django accepts the request after Nginx",[66,50538,50539],{},"upload storage works",[66,50541,50542],{},"disk space and temp areas are adequate",[66,50544,50545],{},"no upstream or platform layer still enforces a lower limit",[52,50547,50549],{"id":50548},"_6-verify-the-fix-end-to-end","6. Verify the fix end to end",[16,50551,50552],{},"Test with three file sizes:",[1173,50554,50555,50558,50561],{},[66,50556,50557],{},"a small file that should always pass",[66,50559,50560],{},"a file near the configured limit that should pass",[66,50562,50563],{},"a file above the limit that should fail predictably",[16,50565,50566,50567,50569],{},"You can test through the application UI or with ",[20,50568,2764],{}," for a multipart endpoint:",[106,50571,50573],{"className":108,"code":50572,"language":110,"meta":111,"style":111},"curl -i -X POST \\\n  -F \"file=@.\u002Ftest-upload-20mb.bin\" \\\n  https:\u002F\u002Fexample.com\u002Fapi\u002Fuploads\u002F\n",[20,50574,50575,50589,50598],{"__ignoreMap":111},[115,50576,50577,50579,50581,50584,50587],{"class":117,"line":118},[115,50578,2764],{"class":262},[115,50580,14187],{"class":202},[115,50582,50583],{"class":202}," -X",[115,50585,50586],{"class":132}," POST",[115,50588,317],{"class":202},[115,50590,50591,50593,50596],{"class":117,"line":136},[115,50592,479],{"class":202},[115,50594,50595],{"class":132}," \"file=@.\u002Ftest-upload-20mb.bin\"",[115,50597,317],{"class":202},[115,50599,50600],{"class":117,"line":149},[115,50601,50602],{"class":132},"  https:\u002F\u002Fexample.com\u002Fapi\u002Fuploads\u002F\n",[16,50604,50605],{},"After testing, review logs again:",[106,50607,50609],{"className":108,"code":50608,"language":110,"meta":111,"style":111},"sudo tail -n 50 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\nsudo tail -n 50 \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[20,50610,50611,50623],{"__ignoreMap":111},[115,50612,50613,50615,50617,50619,50621],{"class":117,"line":118},[115,50614,2001],{"class":262},[115,50616,13188],{"class":132},[115,50618,2794],{"class":202},[115,50620,15523],{"class":202},[115,50622,13195],{"class":132},[115,50624,50625,50627,50629,50631,50633],{"class":117,"line":136},[115,50626,2001],{"class":262},[115,50628,13188],{"class":132},[115,50630,2794],{"class":202},[115,50632,15523],{"class":202},[115,50634,30354],{"class":132},[16,50636,50637],{},"Also confirm Django actually received and stored the file successfully.",[16,50639,50640],{},"Document the chosen max upload size in deployment notes or the repo’s ops documentation.",[11,50642,1321],{"id":1320},[16,50644,12998,50645,50647],{},[20,50646,13316],{}," directive controls how much request body data Nginx will accept. If the upload exceeds that value, Nginx returns 413 before proxying the request upstream. That is why Django often appears uninvolved.",[16,50649,50650,50651,50654,50655,50658],{},"Using a ",[1226,50652,50653],{},"site-level"," limit is simplest when the whole app has one upload policy. A ",[1226,50656,50657],{},"location-level"," limit is better when only a few endpoints need larger files. This keeps the rest of the app on stricter defaults.",[16,50660,50661,50662,50665],{},"Do not set ",[20,50663,50664],{},"client_max_body_size 0"," unless you fully understand the consequences. Unlimited request bodies increase the risk of:",[63,50667,50668,50671,50674],{},[66,50669,50670],{},"denial-of-service through oversized uploads",[66,50672,50673],{},"disk exhaustion from temp files",[66,50675,50676],{},"long-running requests tying up workers and network resources",[16,50678,50679],{},"Restrict upload endpoints where possible:",[63,50681,50682,50685,50688,50691],{},[66,50683,50684],{},"require authentication",[66,50686,50687],{},"validate file types and sizes in Django",[66,50689,50690],{},"add request logging and alerting for repeated 413s or unusually large requests",[66,50692,50693],{},"review request timeout settings if uploads happen over slow links",[52,50695,50697],{"id":50696},"when-to-turn-this-into-a-reusable-template","When to turn this into a reusable template",[16,50699,50700,50701,50703],{},"If you manage more than one Django service, upload-size changes become repetitive and error-prone. This is a good candidate for a version-controlled Nginx template plus a small deploy script that backs up config, renders environment-specific limits, runs ",[20,50702,7611],{},", reloads only on success, and performs an upload smoke test.",[11,50705,1337],{"id":1336},[63,50707,50708,50714,50724,50730,50736,50742,50748],{},[66,50709,50710,50713],{},[1226,50711,50712],{},"Another proxy still blocks uploads:"," A CDN, ingress, or cloud edge proxy may still return 413 even after Nginx is updated.",[66,50715,50716,50719,50720,50723],{},[1226,50717,50718],{},"Wrong file edited:"," Editing ",[20,50721,50722],{},"sites-available"," without the matching symlink or editing an unused config file will have no effect.",[66,50725,50726,50729],{},[1226,50727,50728],{},"Nginx not reloaded:"," The new setting does nothing until reload succeeds.",[66,50731,50732,50735],{},[1226,50733,50734],{},"Failure moves downstream:"," After fixing Nginx, uploads may then fail in Django validation, storage backend writes, or app server timeouts.",[66,50737,50738,50741],{},[1226,50739,50740],{},"Accepted uploads may still use temp disk:"," Increasing the limit can shift pressure to Nginx temp storage and disk I\u002FO.",[66,50743,50744,50747],{},[1226,50745,50746],{},"Static and media are separate concerns:"," This fix affects incoming request bodies, not serving static files or media URLs directly.",[66,50749,50750,50753,50754,50757],{},[1226,50751,50752],{},"TLS and proxy headers:"," Keep your existing ",[20,50755,50756],{},"proxy_set_header"," values intact when editing location blocks so Django still receives correct host and scheme information.",[11,50759,1386],{"id":1385},[63,50761,50762,50768,50773,50779],{},[66,50763,32206,50764,211],{},[1395,50765,50767],{"href":50766},"\u002Ffix-issues\u002Fdjango-media-files-not-serving-uploads-broken","How Django file uploads work in production behind Nginx and Gunicorn",[66,50769,50770,50771,211],{},"If your base stack is not stable yet, start with ",[1395,50772,2986],{"href":2985},[66,50774,50775,50776,211],{},"If uploaded files are accepted but not served correctly, review ",[1395,50777,50778],{"href":50766},"Configure Django media file handling in production",[66,50780,50781,50782,211],{},"If requests fail at the proxy layer in other ways, use ",[1395,50783,50784],{"href":4455},"Debug Django 502 Bad Gateway errors with Nginx and Gunicorn",[11,50786,1420],{"id":1419},[52,50788,47982,50790,50792],{"id":50789},"should-i-set-client_max_body_size-0-for-django-uploads",[20,50791,50664],{}," for Django uploads?",[16,50794,50795,50796,50798],{},"Usually no. ",[20,50797,1094],{}," disables the limit, which increases abuse and resource exhaustion risk. Set a deliberate maximum based on your real file-size requirement.",[52,50800,50802],{"id":50801},"why-do-small-uploads-work-but-large-files-return-413","Why do small uploads work but large files return 413?",[16,50804,50805],{},"Because Nginx accepts request bodies up to its configured limit and rejects anything larger. Small files stay under the threshold; larger ones do not.",[52,50807,50809],{"id":50808},"do-i-need-to-change-django-settings-as-well-as-nginx","Do I need to change Django settings as well as Nginx?",[16,50811,50812],{},"Sometimes. Nginx must allow the request first, but Django settings, validation logic, storage backends, temp storage, and app server timeouts may still affect whether the upload succeeds end to end.",[52,50814,50816],{"id":50815},"can-i-set-different-upload-limits-per-endpoint","Can I set different upload limits per endpoint?",[16,50818,50819,50820,50822,50823,50825,50826,50828],{},"Yes. You can define ",[20,50821,13316],{}," inside a specific ",[20,50824,7128],{}," block, such as ",[20,50827,50110],{},", while keeping stricter limits elsewhere.",[52,50830,50832],{"id":50831},"what-should-i-check-if-413-still-happens-after-updating-nginx","What should I check if 413 still happens after updating Nginx?",[16,50834,46597],{},[1173,50836,50837,50840,50845,50853,50856],{},[66,50838,50839],{},"confirm the correct Nginx config file was edited",[66,50841,4183,50842,50844],{},[20,50843,7611],{}," passed and reload happened",[66,50846,50847,50848,1153,50850,50852],{},"confirm no other ",[20,50849,7128],{},[20,50851,2163],{},", or broader config block overrides the value",[66,50854,50855],{},"check for another reverse proxy, ingress, CDN, or load balancer limit",[66,50857,50858],{},"inspect logs again to see which component now returns the 413",[1485,50860,4517],{},{"title":111,"searchDepth":149,"depth":149,"links":50862},[50863,50864,50865,50882,50885,50886,50887],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":50866},[50867,50868,50869,50874,50875,50881],{"id":49722,"depth":149,"text":49723},{"id":49828,"depth":149,"text":49829},{"id":49935,"depth":149,"text":50870,"children":50871},"3. Apply the Nginx 413 Django fix with client_max_body_size",[50872,50873],{"id":49961,"depth":162,"text":49962},{"id":50103,"depth":162,"text":50104},{"id":50282,"depth":149,"text":50283},{"id":50383,"depth":149,"text":50384,"children":50876},[50877,50878,50879,50880],{"id":50390,"depth":162,"text":50391},{"id":50447,"depth":162,"text":50448},{"id":50483,"depth":162,"text":50484},{"id":50504,"depth":162,"text":50505},{"id":50548,"depth":149,"text":50549},{"id":1320,"depth":136,"text":1321,"children":50883},[50884],{"id":50696,"depth":149,"text":50697},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":50888},[50889,50891,50892,50893,50894],{"id":50789,"depth":149,"text":50890},"Should I set client_max_body_size 0 for Django uploads?",{"id":50801,"depth":149,"text":50802},{"id":50808,"depth":149,"text":50809},{"id":50815,"depth":149,"text":50816},{"id":50831,"depth":149,"text":50832},"If a Django file upload fails with 413 Request Entity Too Large, the rejection usually happens before Django sees the request. In a production stack like:",{},"\u002Ffix-nginx-413-django-uploads","9",[4455,4551,6332],{"title":49635,"description":50895},[1557,2156],"fix-nginx-413-django-uploads",[1557,2156],"piZDTYSEwj7LTc0mV4hVbguUOYjBmsBA9-FCtYQ5YWA",{"id":50906,"title":50907,"body":50908,"category":4543,"description":51679,"difficulty":1543,"extension":1544,"funnel_stage":48822,"intent":4546,"meta":51680,"navigation":309,"path":51681,"priority":35628,"related":51682,"role":4552,"section":4553,"seo":51683,"stack":51684,"stem":51685,"tags":51686,"type":1561,"__hash__":51688},"articles\u002Ffix-nginx-not-connecting-to-gunicorn-connection-refused.md","Fix Nginx Not Connecting to Gunicorn (Connection Refused)",{"type":8,"value":50909,"toc":51648},[50910,50912,50924,50927,50930,50932,50934,50936,50960,50962,50964,50966,50970,50973,50978,50981,50992,50994,50996,50999,51003,51018,51020,51026,51029,51032,51047,51051,51070,51072,51083,51087,51090,51108,51110,51119,51122,51133,51135,51144,51146,51149,51161,51164,51174,51177,51187,51190,51194,51206,51209,51214,51216,51221,51225,51239,51242,51246,51260,51262,51271,51273,51275,51277,51281,51283,51308,51310,51314,51317,51325,51328,51330,51334,51336,51348,51351,51359,51361,51365,51367,51375,51377,51381,51383,51410,51412,51415,51417,51432,51434,51438,51450,51453,51470,51473,51497,51499,51501,51538,51540,51553,51555,51557,51573,51575,51577,51581,51590,51594,51602,51606,51608,51616,51618,51620,51623,51628,51631,51639,51641,51643,51646],[16,50911,48840],{},[63,50913,50914,50918,50921],{},[66,50915,50916],{},[20,50917,31152],{},[66,50919,50920],{},"Nginx returns 502 Bad Gateway",[66,50922,50923],{},"Your Django app is not reachable",[16,50925,50926],{},"👉 Then Nginx cannot connect to Gunicorn.",[16,50928,50929],{},"This guide will help you identify and fix the issue step-by-step.",[23099,50931],{},[11,50933,48092],{"id":48091},[16,50935,33361],{},[106,50937,50938],{"className":108,"code":48097,"language":110,"meta":111,"style":111},[20,50939,50940,50950],{"__ignoreMap":111},[115,50941,50942,50944,50946,50948],{"class":117,"line":118},[115,50943,2001],{"class":262},[115,50945,3480],{"class":132},[115,50947,3483],{"class":132},[115,50949,1987],{"class":132},[115,50951,50952,50954,50956,50958],{"class":117,"line":136},[115,50953,2001],{"class":262},[115,50955,3480],{"class":132},[115,50957,3483],{"class":132},[115,50959,1996],{"class":132},[16,50961,48920],{},[16,50963,48136],{},[23099,50965],{},[11,50967,50969],{"id":50968},"what-connection-refused-means","🧠 What “Connection Refused” Means",[16,50971,50972],{},"This error means:",[48158,50974,50975],{},[16,50976,50977],{},"Nginx tried to connect to Gunicorn, but nothing was listening",[16,50979,50980],{},"Common reasons:",[63,50982,50983,50986,50989],{},[66,50984,50985],{},"Gunicorn is not running",[66,50987,50988],{},"Wrong port\u002Fsocket",[66,50990,50991],{},"Misconfiguration",[23099,50993],{},[11,50995,48168],{"id":48167},[16,50997,50998],{},"Follow these steps carefully.",[52,51000,51002],{"id":51001},"_1-check-if-gunicorn-is-running","1. Check if Gunicorn is running",[106,51004,51006],{"className":108,"code":51005,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn\n",[20,51007,51008],{"__ignoreMap":111},[115,51009,51010,51012,51014,51016],{"class":117,"line":118},[115,51011,2001],{"class":262},[115,51013,3480],{"class":132},[115,51015,1984],{"class":132},[115,51017,1987],{"class":132},[16,51019,49017],{},[63,51021,51022],{},[66,51023,51024],{},[20,51025,30925],{},[16,51027,51028],{},"If NOT running:",[16,51030,51031],{},"Start it:",[106,51033,51035],{"className":108,"code":51034,"language":110,"meta":111,"style":111},"sudo systemctl start gunicorn\n",[20,51036,51037],{"__ignoreMap":111},[115,51038,51039,51041,51043,51045],{"class":117,"line":118},[115,51040,2001],{"class":262},[115,51042,3480],{"class":132},[115,51044,15489],{"class":132},[115,51046,1987],{"class":132},[52,51048,51050],{"id":51049},"_2-check-gunicorn-logs","2. Check Gunicorn logs",[106,51052,51054],{"className":108,"code":51053,"language":110,"meta":111,"style":111},"journalctl -u gunicorn --no-pager -n 50\n",[20,51055,51056],{"__ignoreMap":111},[115,51057,51058,51060,51062,51064,51066,51068],{"class":117,"line":118},[115,51059,2785],{"class":262},[115,51061,2788],{"class":202},[115,51063,2791],{"class":132},[115,51065,46703],{"class":202},[115,51067,2794],{"class":202},[115,51069,19841],{"class":202},[16,51071,31301],{},[63,51073,51074,51077,51080],{},[66,51075,51076],{},"Python errors",[66,51078,51079],{},"Import failures",[66,51081,51082],{},"Missing packages",[52,51084,51086],{"id":51085},"_3-check-what-gunicorn-is-listening-on","3. Check what Gunicorn is listening on",[16,51088,51089],{},"If using TCP:",[106,51091,51093],{"className":108,"code":51092,"language":110,"meta":111,"style":111},"ss -tulnp | grep 8000\n",[20,51094,51095],{"__ignoreMap":111},[115,51096,51097,51099,51102,51104,51106],{"class":117,"line":118},[115,51098,6472],{"class":262},[115,51100,51101],{"class":202}," -tulnp",[115,51103,579],{"class":121},[115,51105,4838],{"class":262},[115,51107,23864],{"class":202},[16,51109,49017],{},[106,51111,51113],{"className":48125,"code":51112,"language":48127,"meta":111,"style":111},"LISTEN 0 128 127.0.0.1:8000\n",[20,51114,51115],{"__ignoreMap":111},[115,51116,51117],{"class":117,"line":118},[115,51118,51112],{},[16,51120,51121],{},"If using socket:",[106,51123,51125],{"className":108,"code":51124,"language":110,"meta":111,"style":111},"ls \u002Fvar\u002Fwww\u002Fmyproject\u002F\n",[20,51126,51127],{"__ignoreMap":111},[115,51128,51129,51131],{"class":117,"line":118},[115,51130,532],{"class":262},[115,51132,48975],{"class":132},[16,51134,31301],{},[106,51136,51138],{"className":48125,"code":51137,"language":48127,"meta":111,"style":111},"gunicorn.sock\n",[20,51139,51140],{"__ignoreMap":111},[115,51141,51142],{"class":117,"line":118},[115,51143,51137],{},[52,51145,48282],{"id":48281},[16,51147,51148],{},"Open:",[106,51150,51151],{"className":108,"code":48288,"language":110,"meta":111,"style":111},[20,51152,51153],{"__ignoreMap":111},[115,51154,51155,51157,51159],{"class":117,"line":118},[115,51156,2001],{"class":262},[115,51158,12408],{"class":132},[115,51160,48299],{"class":132},[16,51162,51163],{},"For TCP setup:",[106,51165,51166],{"className":2154,"code":12982,"language":2156,"meta":111,"style":111},[20,51167,51168],{"__ignoreMap":111},[115,51169,51170,51172],{"class":117,"line":118},[115,51171,12989],{"class":121},[115,51173,3748],{"class":125},[16,51175,51176],{},"For socket setup:",[106,51178,51179],{"className":2154,"code":49164,"language":2156,"meta":111,"style":111},[20,51180,51181],{"__ignoreMap":111},[115,51182,51183,51185],{"class":117,"line":118},[115,51184,12989],{"class":121},[115,51186,49173],{"class":125},[16,51188,51189],{},"👉 These must match Gunicorn exactly",[52,51191,51193],{"id":51192},"_5-test-gunicorn-directly","5. Test Gunicorn directly",[106,51195,51197],{"className":108,"code":51196,"language":110,"meta":111,"style":111},"curl http:\u002F\u002F127.0.0.1:8000\n",[20,51198,51199],{"__ignoreMap":111},[115,51200,51201,51203],{"class":117,"line":118},[115,51202,2764],{"class":262},[115,51204,51205],{"class":132}," http:\u002F\u002F127.0.0.1:8000\n",[16,51207,51208],{},"If fails:",[63,51210,51211],{},[66,51212,51213],{},"Gunicorn is not running correctly",[16,51215,48273],{},[63,51217,51218],{},[66,51219,51220],{},"Problem is Nginx configuration",[52,51222,51224],{"id":51223},"_6-restart-nginx","6. Restart Nginx",[106,51226,51227],{"className":108,"code":48364,"language":110,"meta":111,"style":111},[20,51228,51229],{"__ignoreMap":111},[115,51230,51231,51233,51235,51237],{"class":117,"line":118},[115,51232,2001],{"class":262},[115,51234,3480],{"class":132},[115,51236,3483],{"class":132},[115,51238,1996],{"class":132},[16,51240,51241],{},"Then check your site again.",[52,51243,51245],{"id":51244},"_7-check-nginx-logs","7. Check Nginx logs",[106,51247,51248],{"className":108,"code":48533,"language":110,"meta":111,"style":111},[20,51249,51250],{"__ignoreMap":111},[115,51251,51252,51254,51256,51258],{"class":117,"line":118},[115,51253,2001],{"class":262},[115,51255,13188],{"class":132},[115,51257,2777],{"class":202},[115,51259,13195],{"class":132},[16,51261,31301],{},[63,51263,51264,51266,51269],{},[66,51265,46565],{},[66,51267,51268],{},"no such file",[66,51270,48556],{},[23099,51272],{},[11,51274,48420],{"id":48419},[23099,51276],{},[52,51278,51280],{"id":51279},"gunicorn-not-running","🔴 Gunicorn not running",[16,51282,48429],{},[106,51284,51286],{"className":108,"code":51285,"language":110,"meta":111,"style":111},"sudo systemctl start gunicorn\nsudo systemctl enable gunicorn\n",[20,51287,51288,51298],{"__ignoreMap":111},[115,51289,51290,51292,51294,51296],{"class":117,"line":118},[115,51291,2001],{"class":262},[115,51293,3480],{"class":132},[115,51295,15489],{"class":132},[115,51297,1987],{"class":132},[115,51299,51300,51302,51304,51306],{"class":117,"line":136},[115,51301,2001],{"class":262},[115,51303,3480],{"class":132},[115,51305,8567],{"class":132},[115,51307,1987],{"class":132},[23099,51309],{},[52,51311,51313],{"id":51312},"wrong-port-or-socket","🔴 Wrong port or socket",[16,51315,51316],{},"Mismatch between:",[63,51318,51319,51322],{},[66,51320,51321],{},"Gunicorn bind",[66,51323,51324],{},"Nginx proxy_pass",[16,51326,51327],{},"Fix by aligning both",[23099,51329],{},[52,51331,51333],{"id":51332},"gunicorn-crashed","🔴 Gunicorn crashed",[16,51335,5438],{},[106,51337,51338],{"className":108,"code":49402,"language":110,"meta":111,"style":111},[20,51339,51340],{"__ignoreMap":111},[115,51341,51342,51344,51346],{"class":117,"line":118},[115,51343,2785],{"class":262},[115,51345,2788],{"class":202},[115,51347,1987],{"class":132},[16,51349,51350],{},"Common issues:",[63,51352,51353,51356],{},[66,51354,51355],{},"Syntax errors",[66,51357,51358],{},"Missing dependencies",[23099,51360],{},[52,51362,51364],{"id":51363},"socket-file-missing","🔴 Socket file missing",[16,51366,48429],{},[63,51368,51369,51372],{},[66,51370,51371],{},"Ensure Gunicorn creates socket",[66,51373,51374],{},"Check working directory",[23099,51376],{},[52,51378,51380],{"id":51379},"permission-denied-on-socket","🔴 Permission denied on socket",[16,51382,48429],{},[106,51384,51386],{"className":108,"code":51385,"language":110,"meta":111,"style":111},"sudo chown -R www-data:www-data \u002Fvar\u002Fwww\u002Fmyproject\nsudo chmod 755 \u002Fvar\u002Fwww\u002Fmyproject\n",[20,51387,51388,51400],{"__ignoreMap":111},[115,51389,51390,51392,51394,51396,51398],{"class":117,"line":118},[115,51391,2001],{"class":262},[115,51393,6733],{"class":132},[115,51395,6736],{"class":202},[115,51397,6739],{"class":132},[115,51399,48398],{"class":132},[115,51401,51402,51404,51406,51408],{"class":117,"line":136},[115,51403,2001],{"class":262},[115,51405,12480],{"class":132},[115,51407,48409],{"class":202},[115,51409,48398],{"class":132},[23099,51411],{},[16,51413,51414],{},"🔴 Firewall blocking connection",[16,51416,51089],{},[106,51418,51420],{"className":108,"code":51419,"language":110,"meta":111,"style":111},"sudo ufw allow 8000\n",[20,51421,51422],{"__ignoreMap":111},[115,51423,51424,51426,51428,51430],{"class":117,"line":118},[115,51425,2001],{"class":262},[115,51427,2014],{"class":132},[115,51429,14341],{"class":132},[115,51431,23864],{"class":202},[11,51433,48526],{"id":48525},[52,51435,51437],{"id":51436},"check-listening-ports","Check listening ports",[106,51439,51441],{"className":108,"code":51440,"language":110,"meta":111,"style":111},"ss -tulnp\n",[20,51442,51443],{"__ignoreMap":111},[115,51444,51445,51447],{"class":117,"line":118},[115,51446,6472],{"class":262},[115,51448,51449],{"class":202}," -tulnp\n",[16,51451,51452],{},"Check Gunicorn process",[106,51454,51456],{"className":108,"code":51455,"language":110,"meta":111,"style":111},"ps aux | grep gunicorn\n",[20,51457,51458],{"__ignoreMap":111},[115,51459,51460,51462,51464,51466,51468],{"class":117,"line":118},[115,51461,4830],{"class":262},[115,51463,4833],{"class":132},[115,51465,579],{"class":121},[115,51467,4838],{"class":262},[115,51469,1987],{"class":132},[16,51471,51472],{},"Restart everything cleanly",[106,51474,51475],{"className":108,"code":48097,"language":110,"meta":111,"style":111},[20,51476,51477,51487],{"__ignoreMap":111},[115,51478,51479,51481,51483,51485],{"class":117,"line":118},[115,51480,2001],{"class":262},[115,51482,3480],{"class":132},[115,51484,3483],{"class":132},[115,51486,1987],{"class":132},[115,51488,51489,51491,51493,51495],{"class":117,"line":136},[115,51490,2001],{"class":262},[115,51492,3480],{"class":132},[115,51494,3483],{"class":132},[115,51496,1996],{"class":132},[23099,51498],{},[11,51500,48609],{"id":48608},[63,51502,51504,51510,51516,51522,51528,51533],{"className":51503},[48613],[66,51505,51507,51509],{"className":51506},[48617],[48619,51508],{"disabled":309,"type":48621}," Gunicorn is running",[66,51511,51513,51515],{"className":51512},[48617],[48619,51514],{"disabled":309,"type":48621}," Correct port\u002Fsocket configured",[66,51517,51519,51521],{"className":51518},[48617],[48619,51520],{"disabled":309,"type":48621}," Nginx config matches Gunicorn",[66,51523,51525,51527],{"className":51524},[48617],[48619,51526],{"disabled":309,"type":48621}," No errors in logs",[66,51529,51531,48646],{"className":51530},[48617],[48619,51532],{"disabled":309,"type":48621},[66,51534,51536,48652],{"className":51535},[48617],[48619,51537],{"disabled":309,"type":48621},[23099,51539],{},[48656,51541,51543],{"title":51542,"type":49477},"If Nginx can connect again, validate the rest of the path",[16,51544,51545,51546,51548,51549,51552],{},"Compare your socket setup with the full ",[1395,51547,48671],{"href":48670}," and check the dedicated ",[1395,51550,51551],{"href":48695},"502 Bad Gateway guide"," if the error changes.",[23099,51554],{},[11,51556,48678],{"id":48677},[63,51558,51559,51563,51567],{},[66,51560,51561],{},[1395,51562,48696],{"href":48695},[66,51564,51565],{},[1395,51566,48690],{"href":48670},[66,51568,51569],{},[1395,51570,51572],{"href":51571},"\u002Ffix-issues\u002Fgunicorn-socket-permission-denied-fix-guide","Fix Gunicorn socket permission denied",[23099,51574],{},[11,51576,48701],{"id":1419},[52,51578,51580],{"id":51579},"what-causes-connection-refused","What causes “connection refused”?",[63,51582,51583,51586,51588],{},[66,51584,51585],{},"Gunicorn not running",[66,51587,50988],{},[66,51589,50991],{},[52,51591,51593],{"id":51592},"should-i-use-socket-or-tcp","Should I use socket or TCP?",[63,51595,51596,51599],{},[66,51597,51598],{},"Socket → better performance",[66,51600,51601],{},"TCP → easier debugging",[52,51603,51605],{"id":51604},"why-does-it-work-locally-but-not-in-production","Why does it work locally but not in production?",[16,51607,48745],{},[63,51609,51610,51613],{},[66,51611,51612],{},"Production uses Nginx + Gunicorn",[66,51614,51615],{},"Local uses Django dev server",[23099,51617],{},[11,51619,48757],{"id":48756},[16,51621,51622],{},"“Connection refused” means:",[48158,51624,51625],{},[16,51626,51627],{},"Nginx cannot reach Gunicorn",[16,51629,51630],{},"Fix it by checking:",[63,51632,51633,51636],{},[66,51634,51635],{},"Gunicorn status",[66,51637,51638],{},"Ports\u002Fsockets\n*Configuration alignment",[23099,51640],{},[16,51642,48781],{},[16,51644,51645],{},"A repeatable, tested setup saves hours of debugging.",[1485,51647,48787],{},{"title":111,"searchDepth":149,"depth":149,"links":51649},[51650,51651,51652,51661,51668,51671,51672,51673,51678],{"id":48091,"depth":136,"text":48092},{"id":50968,"depth":136,"text":50969},{"id":48167,"depth":136,"text":48168,"children":51653},[51654,51655,51656,51657,51658,51659,51660],{"id":51001,"depth":149,"text":51002},{"id":51049,"depth":149,"text":51050},{"id":51085,"depth":149,"text":51086},{"id":48281,"depth":149,"text":48282},{"id":51192,"depth":149,"text":51193},{"id":51223,"depth":149,"text":51224},{"id":51244,"depth":149,"text":51245},{"id":48419,"depth":136,"text":48420,"children":51662},[51663,51664,51665,51666,51667],{"id":51279,"depth":149,"text":51280},{"id":51312,"depth":149,"text":51313},{"id":51332,"depth":149,"text":51333},{"id":51363,"depth":149,"text":51364},{"id":51379,"depth":149,"text":51380},{"id":48525,"depth":136,"text":48526,"children":51669},[51670],{"id":51436,"depth":149,"text":51437},{"id":48608,"depth":136,"text":48609},{"id":48677,"depth":136,"text":48678},{"id":1419,"depth":136,"text":48701,"children":51674},[51675,51676,51677],{"id":51579,"depth":149,"text":51580},{"id":51592,"depth":149,"text":51593},{"id":51604,"depth":149,"text":51605},{"id":48756,"depth":136,"text":48757},"Learn how to troubleshoot and fix the 'Connection Refused' error when Nginx fails to connect to Gunicorn. This guide covers common causes and solutions to get your web application running smoothly.",{},"\u002Ffix-nginx-not-connecting-to-gunicorn-connection-refused",[48695,51571,48670],{"title":50907,"description":51679},[],"fix-nginx-not-connecting-to-gunicorn-connection-refused",[2156,14954,51687],"connection-refused","VDU6mb6c-Kp5PkgOHfNa-DPVNkK1U_Phl69lz9WdodQ",{"id":51690,"title":51691,"body":51692,"category":4543,"description":53465,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":53466,"navigation":309,"path":53467,"priority":14025,"related":53468,"role":4552,"section":4553,"seo":53469,"stack":53470,"stem":53471,"tags":53472,"type":4558,"__hash__":53473},"articles\u002Ffix-gunicorn-worker-timeout-django.md","Gunicorn Worker Timeout in Django: Root Causes and Fixes",{"type":8,"value":51693,"toc":53424},[51694,51696,51706,51709,51723,51726,51728,51731,51757,51759,51763,51766,51799,51802,51808,51811,51836,51839,51842,51873,51876,51881,51894,51896,51900,51904,51907,51924,51929,52032,52036,52039,52101,52111,52115,52118,52173,52176,52180,52183,52222,52225,52229,52232,52275,52278,52282,52285,52289,52292,52306,52310,52313,52315,52319,52325,52394,52397,52480,52482,52517,52520,52525,52527,52531,52534,52551,52554,52759,52762,52787,52790,52792,52796,52799,52801,52847,52850,52867,52870,52873,52905,52910,52933,52939,52941,52945,52948,52998,53001,53004,53022,53025,53047,53049,53053,53056,53085,53088,53125,53128,53145,53148,53150,53154,53157,53160,53183,53186,53238,53241,53264,53267,53269,53275,53277,53283,53289,53306,53309,53312,53314,53318,53321,53325,53328,53332,53335,53339,53342,53346,53351,53353,53376,53378,53384,53390,53394,53397,53401,53404,53408,53414,53418,53421],[11,51695,14],{"id":13},[16,51697,51698,51699,51701,51702,51705],{},"A Gunicorn worker timeout in Django production usually means a worker process stopped responding before Gunicorn’s ",[20,51700,47977],{}," limit expired. In logs, this often appears as ",[20,51703,51704],{},"WORKER TIMEOUT"," followed by a worker restart.",[16,51707,51708],{},"That is not the same as:",[63,51710,51711,51714,51717,51720],{},[66,51712,51713],{},"a reverse proxy timeout from Nginx or Caddy",[66,51715,51716],{},"a slow PostgreSQL query or lock by itself",[66,51718,51719],{},"a failed deploy where Gunicorn never started cleanly",[66,51721,51722],{},"an OOM kill from the host kernel",[16,51724,51725],{},"The real deployment problem is that requests are hanging or taking too long somewhere in the request path. Raising the timeout blindly can hide bad application behavior and increase recovery time under load.",[11,51727,30],{"id":29},[16,51729,51730],{},"If you see a Gunicorn worker timeout in Django, do this first:",[1173,51732,51733,51736,51739,51742,51745,51751,51754],{},[66,51734,51735],{},"Confirm the timeout is coming from Gunicorn, not only from Nginx.",[66,51737,51738],{},"Find whether the problem affects one route or the whole app.",[66,51740,51741],{},"Check for slow views, expensive queries, external API calls, wrong worker settings, memory pressure, or static\u002Fmedia requests hitting Django.",[66,51743,51744],{},"Fix the bottleneck first.",[66,51746,51747,51748,51750],{},"Only increase Gunicorn ",[20,51749,47977],{}," for known valid long requests, and keep proxy timeouts aligned.",[66,51752,51753],{},"Verify with logs, controlled test requests, and resource checks.",[66,51755,51756],{},"Keep a rollback path for config and deploy changes.",[11,51758,43],{"id":42},[52,51760,51762],{"id":51761},"_1-confirm-that-the-timeout-is-coming-from-gunicorn","1. Confirm that the timeout is coming from Gunicorn",[16,51764,51765],{},"Check recent Gunicorn logs:",[106,51767,51769],{"className":108,"code":51768,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 100 --no-pager\nsudo journalctl -u gunicorn -f\n",[20,51770,51771,51787],{"__ignoreMap":111},[115,51772,51773,51775,51777,51779,51781,51783,51785],{"class":117,"line":118},[115,51774,2001],{"class":262},[115,51776,5030],{"class":132},[115,51778,2788],{"class":202},[115,51780,2791],{"class":132},[115,51782,2794],{"class":202},[115,51784,2797],{"class":202},[115,51786,2800],{"class":202},[115,51788,51789,51791,51793,51795,51797],{"class":117,"line":136},[115,51790,2001],{"class":262},[115,51792,5030],{"class":132},[115,51794,2788],{"class":202},[115,51796,2791],{"class":132},[115,51798,36482],{"class":202},[16,51800,51801],{},"Look for messages like:",[106,51803,51806],{"className":51804,"code":51805,"language":247,"meta":111},[245],"[CRITICAL] WORKER TIMEOUT (pid:12345)\n[INFO] Booting worker with pid:12346\n",[20,51807,51805],{"__ignoreMap":111},[16,51809,51810],{},"Then compare with Nginx logs:",[106,51812,51814],{"className":108,"code":51813,"language":110,"meta":111,"style":111},"sudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\nsudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[20,51815,51816,51826],{"__ignoreMap":111},[115,51817,51818,51820,51822,51824],{"class":117,"line":118},[115,51819,2001],{"class":262},[115,51821,13188],{"class":132},[115,51823,2777],{"class":202},[115,51825,13195],{"class":132},[115,51827,51828,51830,51832,51834],{"class":117,"line":136},[115,51829,2001],{"class":262},[115,51831,13188],{"class":132},[115,51833,2777],{"class":202},[115,51835,30354],{"class":132},[16,51837,51838],{},"If Nginx shows upstream failures around the same time, correlate the timestamp. A 504 from Nginx does not automatically mean Gunicorn timed out first.",[16,51840,51841],{},"Also test whether the issue is route-specific:",[106,51843,51845],{"className":108,"code":51844,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\ncurl -w \"%{time_total}\\n\" -o \u002Fdev\u002Fnull -s https:\u002F\u002Fexample.com\u002Fproblem-endpoint\u002F\n",[20,51846,51847,51855],{"__ignoreMap":111},[115,51848,51849,51851,51853],{"class":117,"line":118},[115,51850,2764],{"class":262},[115,51852,2767],{"class":202},[115,51854,13426],{"class":132},[115,51856,51857,51859,51861,51864,51866,51868,51870],{"class":117,"line":136},[115,51858,2764],{"class":262},[115,51860,6847],{"class":202},[115,51862,51863],{"class":132}," \"%{time_total}\\n\"",[115,51865,4012],{"class":202},[115,51867,4015],{"class":132},[115,51869,549],{"class":202},[115,51871,51872],{"class":132}," https:\u002F\u002Fexample.com\u002Fproblem-endpoint\u002F\n",[16,51874,51875],{},"If only one endpoint is slow, focus on application logic for that route. If all routes fail, check worker count, startup failures, CPU, memory, or database availability.",[16,51877,51878,51880],{},[1226,51879,3515],{}," confirm at least one of these is true before changing config:",[63,51882,51883,51888,51891],{},[66,51884,51885,51886],{},"Gunicorn logs show ",[20,51887,51704],{},[66,51889,51890],{},"one or more requests stall until Gunicorn restarts a worker",[66,51892,51893],{},"proxy logs clearly match Gunicorn failures by time and route",[23099,51895],{},[52,51897,51899],{"id":51898},"_2-triage-common-root-causes","2. Triage common root causes",[1850,51901,51903],{"id":51902},"slow-django-views-or-blocking-code","Slow Django views or blocking code",[16,51905,51906],{},"Look for views doing heavy work inline:",[63,51908,51909,51912,51915,51918,51921],{},[66,51910,51911],{},"report generation",[66,51913,51914],{},"large queryset iteration",[66,51916,51917],{},"file processing",[66,51919,51920],{},"synchronous HTTP calls",[66,51922,51923],{},"expensive template rendering",[16,51925,6168,51926,51928],{},[20,51927,2707],{},", you can still add targeted timing logs around known problem views:",[106,51930,51932],{"className":2369,"code":51931,"language":1114,"meta":111,"style":111},"import time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef report_view(request):\n    start = time.monotonic()\n    try:\n        # expensive logic\n        ...\n    finally:\n        logger.info(\"report_view duration=%.2fs\", time.monotonic() - start)\n",[20,51933,51934,51941,51947,51951,51963,51967,51976,51986,51993,51998,52003,52010],{"__ignoreMap":111},[115,51935,51936,51938],{"class":117,"line":118},[115,51937,5613],{"class":121},[115,51939,51940],{"class":125}," time\n",[115,51942,51943,51945],{"class":117,"line":136},[115,51944,5613],{"class":121},[115,51946,8775],{"class":125},[115,51948,51949],{"class":117,"line":149},[115,51950,310],{"emptyLinePlaceholder":309},[115,51952,51953,51955,51957,51959,51961],{"class":117,"line":162},[115,51954,8784],{"class":125},[115,51956,129],{"class":121},[115,51958,8789],{"class":125},[115,51960,8792],{"class":202},[115,51962,2394],{"class":125},[115,51964,51965],{"class":117,"line":175},[115,51966,310],{"emptyLinePlaceholder":309},[115,51968,51969,51971,51974],{"class":117,"line":350},[115,51970,8808],{"class":121},[115,51972,51973],{"class":262}," report_view",[115,51975,17271],{"class":125},[115,51977,51978,51981,51983],{"class":117,"line":365},[115,51979,51980],{"class":125},"    start ",[115,51982,129],{"class":121},[115,51984,51985],{"class":125}," time.monotonic()\n",[115,51987,51988,51991],{"class":117,"line":380},[115,51989,51990],{"class":121},"    try",[115,51992,2498],{"class":125},[115,51994,51995],{"class":117,"line":487},[115,51996,51997],{"class":3861},"        # expensive logic\n",[115,51999,52000],{"class":117,"line":2095},[115,52001,52002],{"class":202},"        ...\n",[115,52004,52005,52008],{"class":117,"line":2104},[115,52006,52007],{"class":121},"    finally",[115,52009,2498],{"class":125},[115,52011,52012,52015,52018,52021,52024,52027,52029],{"class":117,"line":2113},[115,52013,52014],{"class":125},"        logger.info(",[115,52016,52017],{"class":132},"\"report_view duration=",[115,52019,52020],{"class":202},"%.2f",[115,52022,52023],{"class":132},"s\"",[115,52025,52026],{"class":125},", time.monotonic() ",[115,52028,36579],{"class":121},[115,52030,52031],{"class":125}," start)\n",[1850,52033,52035],{"id":52034},"long-database-queries-locks-or-missing-indexes","Long database queries, locks, or missing indexes",[16,52037,52038],{},"Check PostgreSQL for slow or blocked queries:",[106,52040,52042],{"className":11064,"code":52041,"language":11066,"meta":111,"style":111},"SELECT pid, now() - query_start AS duration, state, wait_event_type, wait_event, query\nFROM pg_stat_activity\nWHERE state \u003C> 'idle'\nORDER BY query_start ASC;\n",[20,52043,52044,52072,52078,52090],{"__ignoreMap":111},[115,52045,52046,52048,52051,52054,52057,52059,52062,52065,52068,52070],{"class":117,"line":118},[115,52047,11073],{"class":121},[115,52049,52050],{"class":125}," pid, ",[115,52052,52053],{"class":121},"now",[115,52055,52056],{"class":125},"() ",[115,52058,36579],{"class":121},[115,52060,52061],{"class":125}," query_start ",[115,52063,52064],{"class":121},"AS",[115,52066,52067],{"class":125}," duration, ",[115,52069,38462],{"class":121},[115,52071,38465],{"class":125},[115,52073,52074,52076],{"class":117,"line":136},[115,52075,11089],{"class":121},[115,52077,38472],{"class":125},[115,52079,52080,52082,52084,52087],{"class":117,"line":149},[115,52081,11097],{"class":121},[115,52083,47626],{"class":121},[115,52085,52086],{"class":121}," \u003C>",[115,52088,52089],{"class":132}," 'idle'\n",[115,52091,52092,52094,52096,52099],{"class":117,"line":162},[115,52093,38335],{"class":121},[115,52095,52061],{"class":125},[115,52097,52098],{"class":121},"ASC",[115,52100,3811],{"class":125},[16,52102,52103,52104,1153,52107,52110],{},"If a route triggers many ORM queries, reduce query count first with ",[20,52105,52106],{},"select_related()",[20,52108,52109],{},"prefetch_related()",", or better filtering. If one query is slow, inspect its execution plan and indexing.",[1850,52112,52114],{"id":52113},"external-api-calls-blocking-workers","External API calls blocking workers",[16,52116,52117],{},"If a Django view calls another service synchronously, set request timeouts. Do not let calls hang indefinitely.",[106,52119,52121],{"className":2369,"code":52120,"language":1114,"meta":111,"style":111},"import requests\n\nresponse = requests.get(\n    \"https:\u002F\u002Fapi.example.com\u002Fdata\",\n    timeout=(3.05, 10),\n)\n",[20,52122,52123,52130,52134,52144,52151,52169],{"__ignoreMap":111},[115,52124,52125,52127],{"class":117,"line":118},[115,52126,5613],{"class":121},[115,52128,52129],{"class":125}," requests\n",[115,52131,52132],{"class":117,"line":136},[115,52133,310],{"emptyLinePlaceholder":309},[115,52135,52136,52139,52141],{"class":117,"line":149},[115,52137,52138],{"class":125},"response ",[115,52140,129],{"class":121},[115,52142,52143],{"class":125}," requests.get(\n",[115,52145,52146,52149],{"class":117,"line":162},[115,52147,52148],{"class":132},"    \"https:\u002F\u002Fapi.example.com\u002Fdata\"",[115,52150,3354],{"class":125},[115,52152,52153,52156,52158,52160,52163,52165,52167],{"class":117,"line":175},[115,52154,52155],{"class":5680},"    timeout",[115,52157,129],{"class":121},[115,52159,37145],{"class":125},[115,52161,52162],{"class":202},"3.05",[115,52164,1153],{"class":125},[115,52166,16241],{"class":202},[115,52168,10746],{"class":125},[115,52170,52171],{"class":117,"line":350},[115,52172,2394],{"class":125},[16,52174,52175],{},"Use connect and read timeouts explicitly. Add retries carefully, and only for idempotent operations.",[1850,52177,52179],{"id":52178},"insufficient-workers-or-wrong-worker-class","Insufficient workers or wrong worker class",[16,52181,52182],{},"Inspect the running process:",[106,52184,52186],{"className":108,"code":52185,"language":110,"meta":111,"style":111},"ps aux | grep gunicorn\nsystemctl status gunicorn\nss -ltnp | grep ':8000'\n",[20,52187,52188,52200,52208],{"__ignoreMap":111},[115,52189,52190,52192,52194,52196,52198],{"class":117,"line":118},[115,52191,4830],{"class":262},[115,52193,4833],{"class":132},[115,52195,579],{"class":121},[115,52197,4838],{"class":262},[115,52199,1987],{"class":132},[115,52201,52202,52204,52206],{"class":117,"line":136},[115,52203,1981],{"class":262},[115,52205,1984],{"class":132},[115,52207,1987],{"class":132},[115,52209,52210,52212,52215,52217,52219],{"class":117,"line":149},[115,52211,6472],{"class":262},[115,52213,52214],{"class":202}," -ltnp",[115,52216,579],{"class":121},[115,52218,4838],{"class":262},[115,52220,52221],{"class":132}," ':8000'\n",[16,52223,52224],{},"Review whether you are using too few workers for your traffic pattern, or a sync worker model while doing blocking I\u002FO heavily.",[1850,52226,52228],{"id":52227},"cpu-or-memory-pressure","CPU or memory pressure",[16,52230,52231],{},"Check host health:",[106,52233,52235],{"className":108,"code":52234,"language":110,"meta":111,"style":111},"top\nfree -m\nvmstat 1\ndmesg -T | grep -i -E 'killed process|out of memory|oom'\n",[20,52236,52237,52242,52250,52257],{"__ignoreMap":111},[115,52238,52239],{"class":117,"line":118},[115,52240,52241],{"class":262},"top\n",[115,52243,52244,52247],{"class":117,"line":136},[115,52245,52246],{"class":262},"free",[115,52248,52249],{"class":202}," -m\n",[115,52251,52252,52255],{"class":117,"line":149},[115,52253,52254],{"class":262},"vmstat",[115,52256,8995],{"class":202},[115,52258,52259,52262,52264,52266,52268,52270,52272],{"class":117,"line":162},[115,52260,52261],{"class":262},"dmesg",[115,52263,26898],{"class":202},[115,52265,579],{"class":121},[115,52267,4838],{"class":262},[115,52269,14187],{"class":202},[115,52271,6482],{"class":202},[115,52273,52274],{"class":132}," 'killed process|out of memory|oom'\n",[16,52276,52277],{},"If the host is swapping heavily or the kernel is killing workers, the problem may not be Gunicorn timeout at all.",[1850,52279,52281],{"id":52280},"large-file-handling-or-bad-staticmedia-routing","Large file handling or bad static\u002Fmedia routing",[16,52283,52284],{},"If Django is serving static or media files in production, fix that first. Static files should usually be served by Nginx or object storage, not by Django request workers.",[1850,52286,52288],{"id":52287},"startup-tasks-migrations-or-heavy-imports","Startup tasks, migrations, or heavy imports",[16,52290,52291],{},"If timeouts happen right after deploy or restart, check whether worker startup is slow because of:",[63,52293,52294,52297,52300,52303],{},[66,52295,52296],{},"heavy import side effects",[66,52298,52299],{},"app startup code doing network calls",[66,52301,52302],{},"migrations run in the wrong phase",[66,52304,52305],{},"cache warmups inside web startup",[1850,52307,52309],{"id":52308},"background-work-inside-web-requests","Background work inside web requests",[16,52311,52312],{},"If the request starts long-running work, move it to Celery, RQ, or another job system, then return a task status or completion callback path.",[23099,52314],{},[52,52316,52318],{"id":52317},"_3-inspect-current-gunicorn-runtime-configuration","3. Inspect current Gunicorn runtime configuration",[16,52320,52321,52322,52324],{},"Review how Gunicorn is started. Common ",[20,52323,1277],{}," example:",[106,52326,52328],{"className":2026,"code":52327,"language":2028,"meta":111,"style":111},"[Service]\nUser=django\nGroup=www-data\nEnvironmentFile=\u002Fetc\u002Fmyproject\u002Fgunicorn.env\nWorkingDirectory=\u002Fsrv\u002Fmyproject\u002Fcurrent\nExecStart=\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn \\\n    --bind 127.0.0.1:8000 \\\n    --workers 3 \\\n    --threads 2 \\\n    --timeout 30 \\\n    --graceful-timeout 30 \\\n    myproject.wsgi:application\n",[20,52329,52330,52334,52340,52346,52353,52360,52367,52371,52375,52380,52385,52390],{"__ignoreMap":111},[115,52331,52332],{"class":117,"line":118},[115,52333,2060],{"class":262},[115,52335,52336,52338],{"class":117,"line":136},[115,52337,2065],{"class":121},[115,52339,2068],{"class":125},[115,52341,52342,52344],{"class":117,"line":149},[115,52343,2073],{"class":121},[115,52345,2076],{"class":125},[115,52347,52348,52350],{"class":117,"line":162},[115,52349,2089],{"class":121},[115,52351,52352],{"class":125},"=\u002Fetc\u002Fmyproject\u002Fgunicorn.env\n",[115,52354,52355,52357],{"class":117,"line":175},[115,52356,2081],{"class":121},[115,52358,52359],{"class":125},"=\u002Fsrv\u002Fmyproject\u002Fcurrent\n",[115,52361,52362,52364],{"class":117,"line":350},[115,52363,2107],{"class":121},[115,52365,52366],{"class":125},"=\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn \\\n",[115,52368,52369],{"class":117,"line":365},[115,52370,23722],{"class":125},[115,52372,52373],{"class":117,"line":380},[115,52374,15417],{"class":125},[115,52376,52377],{"class":117,"line":487},[115,52378,52379],{"class":125},"    --threads 2 \\\n",[115,52381,52382],{"class":117,"line":2095},[115,52383,52384],{"class":125},"    --timeout 30 \\\n",[115,52386,52387],{"class":117,"line":2104},[115,52388,52389],{"class":125},"    --graceful-timeout 30 \\\n",[115,52391,52392],{"class":117,"line":2113},[115,52393,29753],{"class":125},[16,52395,52396],{},"Or a Gunicorn config file:",[106,52398,52400],{"className":2369,"code":52399,"language":1114,"meta":111,"style":111},"bind = \"127.0.0.1:8000\"\nworkers = 3\nthreads = 2\nworker_class = \"gthread\"\ntimeout = 30\ngraceful_timeout = 30\nmax_requests = 1000\nmax_requests_jitter = 100\n",[20,52401,52402,52412,52422,52432,52442,52452,52461,52471],{"__ignoreMap":111},[115,52403,52404,52407,52409],{"class":117,"line":118},[115,52405,52406],{"class":125},"bind ",[115,52408,129],{"class":121},[115,52410,52411],{"class":132}," \"127.0.0.1:8000\"\n",[115,52413,52414,52417,52419],{"class":117,"line":136},[115,52415,52416],{"class":125},"workers ",[115,52418,129],{"class":121},[115,52420,52421],{"class":202}," 3\n",[115,52423,52424,52427,52429],{"class":117,"line":149},[115,52425,52426],{"class":125},"threads ",[115,52428,129],{"class":121},[115,52430,52431],{"class":202}," 2\n",[115,52433,52434,52437,52439],{"class":117,"line":162},[115,52435,52436],{"class":125},"worker_class ",[115,52438,129],{"class":121},[115,52440,52441],{"class":132}," \"gthread\"\n",[115,52443,52444,52447,52449],{"class":117,"line":175},[115,52445,52446],{"class":125},"timeout ",[115,52448,129],{"class":121},[115,52450,52451],{"class":202}," 30\n",[115,52453,52454,52457,52459],{"class":117,"line":350},[115,52455,52456],{"class":125},"graceful_timeout ",[115,52458,129],{"class":121},[115,52460,52451],{"class":202},[115,52462,52463,52466,52468],{"class":117,"line":365},[115,52464,52465],{"class":125},"max_requests ",[115,52467,129],{"class":121},[115,52469,52470],{"class":202}," 1000\n",[115,52472,52473,52476,52478],{"class":117,"line":380},[115,52474,52475],{"class":125},"max_requests_jitter ",[115,52477,129],{"class":121},[115,52479,46680],{"class":202},[16,52481,5438],{},[63,52483,52484,52488,52493,52498,52503,52508,52511,52514],{},[66,52485,52486],{},[20,52487,47977],{},[66,52489,52490],{},[20,52491,52492],{},"graceful_timeout",[66,52494,52495],{},[20,52496,52497],{},"workers",[66,52499,52500],{},[20,52501,52502],{},"threads",[66,52504,52505],{},[20,52506,52507],{},"worker_class",[66,52509,52510],{},"bind address",[66,52512,52513],{},"environment file path",[66,52515,52516],{},"service user permissions",[16,52518,52519],{},"If you use an environment file, keep secrets readable only by the service user or root.",[16,52521,52522,52524],{},[1226,52523,3515],{}," document the current known-good config before editing it.",[23099,52526],{},[52,52528,52530],{"id":52529},"_4-fix-application-level-causes-before-raising-timeout","4. Fix application-level causes before raising timeout",[16,52532,52533],{},"Start with the slow endpoint, not the Gunicorn number.",[63,52535,52536,52539,52542,52545,52548],{},[66,52537,52538],{},"Reduce ORM query count.",[66,52540,52541],{},"Add indexes for real slow queries.",[66,52543,52544],{},"Remove blocking network calls from request-response flow.",[66,52546,52547],{},"Move long-running work to a queue.",[66,52549,52550],{},"Serve static and media outside Django.",[16,52552,52553],{},"A production Nginx example should include complete proxy headers and TLS directives:",[106,52555,52557],{"className":2154,"code":52556,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server 127.0.0.1:8000;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyproject\u002Fshared\u002Fstatic\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyproject\u002Fshared\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fdjango_app;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-Port $server_port;\n        proxy_connect_timeout 5s;\n        proxy_read_timeout 60s;\n        proxy_send_timeout 60s;\n        proxy_redirect off;\n    }\n}\n",[20,52558,52559,52569,52577,52581,52585,52591,52599,52605,52609,52615,52621,52625,52633,52640,52644,52648,52656,52663,52667,52671,52679,52686,52692,52698,52704,52710,52716,52725,52734,52743,52751,52755],{"__ignoreMap":111},[115,52560,52561,52564,52567],{"class":117,"line":118},[115,52562,52563],{"class":121},"upstream",[115,52565,52566],{"class":262}," django_app ",[115,52568,2220],{"class":125},[115,52570,52571,52574],{"class":117,"line":136},[115,52572,52573],{"class":121},"    server",[115,52575,52576],{"class":125}," 127.0.0.1:8000;\n",[115,52578,52579],{"class":117,"line":149},[115,52580,2323],{"class":125},[115,52582,52583],{"class":117,"line":162},[115,52584,310],{"emptyLinePlaceholder":309},[115,52586,52587,52589],{"class":117,"line":175},[115,52588,2163],{"class":121},[115,52590,2166],{"class":125},[115,52592,52593,52595,52597],{"class":117,"line":350},[115,52594,2171],{"class":121},[115,52596,2174],{"class":202},[115,52598,2177],{"class":125},[115,52600,52601,52603],{"class":117,"line":365},[115,52602,2182],{"class":121},[115,52604,2185],{"class":125},[115,52606,52607],{"class":117,"line":380},[115,52608,310],{"emptyLinePlaceholder":309},[115,52610,52611,52613],{"class":117,"line":487},[115,52612,2194],{"class":121},[115,52614,2197],{"class":125},[115,52616,52617,52619],{"class":117,"line":2095},[115,52618,2202],{"class":121},[115,52620,2205],{"class":125},[115,52622,52623],{"class":117,"line":2104},[115,52624,310],{"emptyLinePlaceholder":309},[115,52626,52627,52629,52631],{"class":117,"line":2113},[115,52628,2214],{"class":121},[115,52630,2217],{"class":262},[115,52632,2220],{"class":125},[115,52634,52635,52637],{"class":117,"line":2122},[115,52636,2225],{"class":121},[115,52638,52639],{"class":125},"\u002Fsrv\u002Fmyproject\u002Fshared\u002Fstatic\u002F;\n",[115,52641,52642],{"class":117,"line":2131},[115,52643,2233],{"class":125},[115,52645,52646],{"class":117,"line":2136},[115,52647,310],{"emptyLinePlaceholder":309},[115,52649,52650,52652,52654],{"class":117,"line":2142},[115,52651,2214],{"class":121},[115,52653,2244],{"class":262},[115,52655,2220],{"class":125},[115,52657,52658,52660],{"class":117,"line":2273},[115,52659,2225],{"class":121},[115,52661,52662],{"class":125},"\u002Fsrv\u002Fmyproject\u002Fshared\u002Fmedia\u002F;\n",[115,52664,52665],{"class":117,"line":2282},[115,52666,2233],{"class":125},[115,52668,52669],{"class":117,"line":2291},[115,52670,310],{"emptyLinePlaceholder":309},[115,52672,52673,52675,52677],{"class":117,"line":2299},[115,52674,2214],{"class":121},[115,52676,2268],{"class":262},[115,52678,2220],{"class":125},[115,52680,52681,52683],{"class":117,"line":2307},[115,52682,2276],{"class":121},[115,52684,52685],{"class":125},"http:\u002F\u002Fdjango_app;\n",[115,52687,52688,52690],{"class":117,"line":2315},[115,52689,2285],{"class":121},[115,52691,2288],{"class":125},[115,52693,52694,52696],{"class":117,"line":2320},[115,52695,2285],{"class":121},[115,52697,2296],{"class":125},[115,52699,52700,52702],{"class":117,"line":7083},[115,52701,2285],{"class":121},[115,52703,2312],{"class":125},[115,52705,52706,52708],{"class":117,"line":7090},[115,52707,2285],{"class":121},[115,52709,2304],{"class":125},[115,52711,52712,52714],{"class":117,"line":7097},[115,52713,2285],{"class":121},[115,52715,7074],{"class":125},[115,52717,52718,52720,52723],{"class":117,"line":7108},[115,52719,12925],{"class":121},[115,52721,52722],{"class":202},"5s",[115,52724,3811],{"class":125},[115,52726,52727,52729,52732],{"class":117,"line":7113},[115,52728,12916],{"class":121},[115,52730,52731],{"class":202},"60s",[115,52733,3811],{"class":125},[115,52735,52736,52739,52741],{"class":117,"line":16535},[115,52737,52738],{"class":121},"        proxy_send_timeout ",[115,52740,52731],{"class":202},[115,52742,3811],{"class":125},[115,52744,52745,52747,52749],{"class":117,"line":16544},[115,52746,7100],{"class":121},[115,52748,7103],{"class":202},[115,52750,3811],{"class":125},[115,52752,52753],{"class":117,"line":16549},[115,52754,2233],{"class":125},[115,52756,52757],{"class":117,"line":16555},[115,52758,2323],{"class":125},[16,52760,52761],{},"If Django is behind Nginx and you rely on forwarded HTTPS headers, make sure Django is configured to trust the proxy correctly:",[106,52763,52765],{"className":2369,"code":52764,"language":1114,"meta":111,"style":111},"# settings.py\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n",[20,52766,52767,52771],{"__ignoreMap":111},[115,52768,52769],{"class":117,"line":118},[115,52770,42173],{"class":3861},[115,52772,52773,52775,52777,52779,52781,52783,52785],{"class":117,"line":136},[115,52774,2377],{"class":202},[115,52776,2380],{"class":121},[115,52778,2383],{"class":125},[115,52780,2386],{"class":132},[115,52782,1153],{"class":125},[115,52784,2391],{"class":132},[115,52786,2394],{"class":125},[16,52788,52789],{},"If the route is legitimately long-running, redesign it before simply waiting longer.",[23099,52791],{},[52,52793,52795],{"id":52794},"_5-apply-safe-gunicorn-changes-when-needed","5. Apply safe Gunicorn changes when needed",[16,52797,52798],{},"If a request is valid but sometimes exceeds 30 seconds, a measured timeout increase may be reasonable.",[16,52800,12414],{},[106,52802,52804],{"className":2369,"code":52803,"language":1114,"meta":111,"style":111},"timeout = 60\ngraceful_timeout = 30\nworkers = 3\nthreads = 2\nworker_class = \"gthread\"\n",[20,52805,52806,52815,52823,52831,52839],{"__ignoreMap":111},[115,52807,52808,52810,52812],{"class":117,"line":118},[115,52809,52446],{"class":125},[115,52811,129],{"class":121},[115,52813,52814],{"class":202}," 60\n",[115,52816,52817,52819,52821],{"class":117,"line":136},[115,52818,52456],{"class":125},[115,52820,129],{"class":121},[115,52822,52451],{"class":202},[115,52824,52825,52827,52829],{"class":117,"line":149},[115,52826,52416],{"class":125},[115,52828,129],{"class":121},[115,52830,52421],{"class":202},[115,52832,52833,52835,52837],{"class":117,"line":162},[115,52834,52426],{"class":125},[115,52836,129],{"class":121},[115,52838,52431],{"class":202},[115,52840,52841,52843,52845],{"class":117,"line":175},[115,52842,52436],{"class":125},[115,52844,129],{"class":121},[115,52846,52441],{"class":132},[16,52848,52849],{},"Use caution:",[63,52851,52852,52858,52864],{},[66,52853,52854,52857],{},[20,52855,52856],{},"sync"," workers are simple and predictable, but one blocked request ties up one worker.",[66,52859,52860,52863],{},[20,52861,52862],{},"gthread"," can help if requests spend time waiting on I\u002FO.",[66,52865,52866],{},"async worker classes require compatible app behavior and should not be adopted casually during an incident.",[16,52868,52869],{},"Validate the exact config file you changed and keep a copy of the previous known-good version before restarting services.",[16,52871,52872],{},"Reload carefully:",[106,52874,52875],{"className":108,"code":31429,"language":110,"meta":111,"style":111},[20,52876,52877,52885,52895],{"__ignoreMap":111},[115,52878,52879,52881,52883],{"class":117,"line":118},[115,52880,2001],{"class":262},[115,52882,3480],{"class":132},[115,52884,4984],{"class":132},[115,52886,52887,52889,52891,52893],{"class":117,"line":136},[115,52888,2001],{"class":262},[115,52890,3480],{"class":132},[115,52892,3483],{"class":132},[115,52894,1987],{"class":132},[115,52896,52897,52899,52901,52903],{"class":117,"line":149},[115,52898,2001],{"class":262},[115,52900,3480],{"class":132},[115,52902,1984],{"class":132},[115,52904,1987],{"class":132},[16,52906,52907,52908,3526],{},"If your unit supports reload safely, confirm it has ",[20,52909,21951],{},[106,52911,52913],{"className":108,"code":52912,"language":110,"meta":111,"style":111},"systemctl cat gunicorn\nsudo systemctl reload gunicorn\n",[20,52914,52915,52923],{"__ignoreMap":111},[115,52916,52917,52919,52921],{"class":117,"line":118},[115,52918,1981],{"class":262},[115,52920,4973],{"class":132},[115,52922,1987],{"class":132},[115,52924,52925,52927,52929,52931],{"class":117,"line":136},[115,52926,2001],{"class":262},[115,52928,3480],{"class":132},[115,52930,3919],{"class":132},[115,52932,1987],{"class":132},[16,52934,52935,52938],{},[1226,52936,52937],{},"Rollback:"," if restart makes behavior worse, restore the previous config and restart Gunicorn again.",[23099,52940],{},[52,52942,52944],{"id":52943},"_6-align-reverse-proxy-timeout-settings","6. Align reverse proxy timeout settings",[16,52946,52947],{},"Review Nginx timeouts so they are intentional, not arbitrary:",[106,52949,52951],{"className":2154,"code":52950,"language":2156,"meta":111,"style":111},"location \u002F {\n    proxy_pass http:\u002F\u002Fdjango_app;\n    proxy_connect_timeout 5s;\n    proxy_read_timeout 60s;\n    proxy_send_timeout 60s;\n}\n",[20,52952,52953,52961,52967,52976,52985,52994],{"__ignoreMap":111},[115,52954,52955,52957,52959],{"class":117,"line":118},[115,52956,7128],{"class":121},[115,52958,2268],{"class":262},[115,52960,2220],{"class":125},[115,52962,52963,52965],{"class":117,"line":136},[115,52964,7137],{"class":121},[115,52966,52685],{"class":125},[115,52968,52969,52972,52974],{"class":117,"line":149},[115,52970,52971],{"class":121},"    proxy_connect_timeout ",[115,52973,52722],{"class":202},[115,52975,3811],{"class":125},[115,52977,52978,52981,52983],{"class":117,"line":162},[115,52979,52980],{"class":121},"    proxy_read_timeout ",[115,52982,52731],{"class":202},[115,52984,3811],{"class":125},[115,52986,52987,52990,52992],{"class":117,"line":175},[115,52988,52989],{"class":121},"    proxy_send_timeout ",[115,52991,52731],{"class":202},[115,52993,3811],{"class":125},[115,52995,52996],{"class":117,"line":350},[115,52997,2323],{"class":125},[16,52999,53000],{},"Do not raise every timeout value together just to stop visible errors. That can turn a fast failure into a long outage per request.",[16,53002,53003],{},"A useful rule is:",[63,53005,53006,53012,53019],{},[66,53007,53008,53009,53011],{},"Gunicorn ",[20,53010,47977],{}," should reflect the longest acceptable in-app request time.",[66,53013,53014,53015,53018],{},"Nginx ",[20,53016,53017],{},"proxy_read_timeout"," should be compatible with that value.",[66,53020,53021],{},"long-running work should still be moved out of the web request path when possible",[16,53023,53024],{},"If you change Nginx, test and reload it explicitly:",[106,53026,53027],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,53028,53029,53037],{"__ignoreMap":111},[115,53030,53031,53033,53035],{"class":117,"line":118},[115,53032,2001],{"class":262},[115,53034,3906],{"class":132},[115,53036,4282],{"class":202},[115,53038,53039,53041,53043,53045],{"class":117,"line":136},[115,53040,2001],{"class":262},[115,53042,3480],{"class":132},[115,53044,3919],{"class":132},[115,53046,1996],{"class":132},[23099,53048],{},[52,53050,53052],{"id":53051},"_7-verify-the-fix","7. Verify the fix",[16,53054,53055],{},"Re-test the affected endpoint:",[106,53057,53059],{"className":108,"code":53058,"language":110,"meta":111,"style":111},"curl -w \"%{time_total}\\n\" -o \u002Fdev\u002Fnull -s https:\u002F\u002Fexample.com\u002Fproblem-endpoint\u002F\ncurl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,53060,53061,53077],{"__ignoreMap":111},[115,53062,53063,53065,53067,53069,53071,53073,53075],{"class":117,"line":118},[115,53064,2764],{"class":262},[115,53066,6847],{"class":202},[115,53068,51863],{"class":132},[115,53070,4012],{"class":202},[115,53072,4015],{"class":132},[115,53074,549],{"class":202},[115,53076,51872],{"class":132},[115,53078,53079,53081,53083],{"class":117,"line":136},[115,53080,2764],{"class":262},[115,53082,2767],{"class":202},[115,53084,13426],{"class":132},[16,53086,53087],{},"Then watch logs and host health:",[106,53089,53091],{"className":108,"code":53090,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -f\nsudo tail -f \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\ntop\nfree -m\n",[20,53092,53093,53105,53115,53119],{"__ignoreMap":111},[115,53094,53095,53097,53099,53101,53103],{"class":117,"line":118},[115,53096,2001],{"class":262},[115,53098,5030],{"class":132},[115,53100,2788],{"class":202},[115,53102,2791],{"class":132},[115,53104,36482],{"class":202},[115,53106,53107,53109,53111,53113],{"class":117,"line":136},[115,53108,2001],{"class":262},[115,53110,13188],{"class":132},[115,53112,2777],{"class":202},[115,53114,13195],{"class":132},[115,53116,53117],{"class":117,"line":149},[115,53118,52241],{"class":262},[115,53120,53121,53123],{"class":117,"line":162},[115,53122,52246],{"class":262},[115,53124,52249],{"class":202},[16,53126,53127],{},"Validate:",[63,53129,53130,53133,53136,53139,53142],{},[66,53131,53132],{},"timeout errors dropped",[66,53134,53135],{},"latency is acceptable",[66,53137,53138],{},"workers are not restarting repeatedly",[66,53140,53141],{},"CPU and memory stayed within limits",[66,53143,53144],{},"database connections and query times are healthy",[16,53146,53147],{},"If possible, test in staging first with controlled load.",[23099,53149],{},[52,53151,53153],{"id":53152},"_8-recovery-and-rollback","8. Recovery and rollback",[16,53155,53156],{},"If the problem started after a deploy, roll back the release before broad timeout changes.",[16,53158,53159],{},"Safe recovery options:",[1173,53161,53162,53168,53171,53174,53177,53180],{},[66,53163,53164,53165,53167],{},"Revert the Gunicorn config file, ",[20,53166,1277],{}," override, or service unit you changed.",[66,53169,53170],{},"Revert the Nginx config too if proxy timeouts were changed during the incident.",[66,53172,53173],{},"Test changed configs before reloading services.",[66,53175,53176],{},"Restart or reload only the affected services.",[66,53178,53179],{},"Roll back the app release if the issue began after code changes.",[66,53181,53182],{},"Temporarily disable the heavy endpoint or traffic source if needed.",[16,53184,53185],{},"Example rollback flow:",[106,53187,53189],{"className":108,"code":53188,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service.bak \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\nsudo systemctl daemon-reload\nsudo systemctl restart gunicorn\nsudo nginx -t\nsudo systemctl reload nginx\n",[20,53190,53191,53202,53210,53220,53228],{"__ignoreMap":111},[115,53192,53193,53195,53197,53200],{"class":117,"line":118},[115,53194,2001],{"class":262},[115,53196,6516],{"class":132},[115,53198,53199],{"class":132}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service.bak",[115,53201,23653],{"class":132},[115,53203,53204,53206,53208],{"class":117,"line":136},[115,53205,2001],{"class":262},[115,53207,3480],{"class":132},[115,53209,4984],{"class":132},[115,53211,53212,53214,53216,53218],{"class":117,"line":149},[115,53213,2001],{"class":262},[115,53215,3480],{"class":132},[115,53217,3483],{"class":132},[115,53219,1987],{"class":132},[115,53221,53222,53224,53226],{"class":117,"line":162},[115,53223,2001],{"class":262},[115,53225,3906],{"class":132},[115,53227,4282],{"class":202},[115,53229,53230,53232,53234,53236],{"class":117,"line":175},[115,53231,2001],{"class":262},[115,53233,3480],{"class":132},[115,53235,3919],{"class":132},[115,53237,1996],{"class":132},[16,53239,53240],{},"That path is only an example. In many deployments, the rollback target may instead be:",[63,53242,53243,53246,53255,53258],{},[66,53244,53245],{},"a Gunicorn config file",[66,53247,53248,53249,53251,53252],{},"a ",[20,53250,1277],{}," drop-in under ",[20,53253,53254],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service.d\u002F",[66,53256,53257],{},"an application release symlink",[66,53259,53260,53261],{},"an Nginx site file under ",[20,53262,53263],{},"\u002Fetc\u002Fnginx\u002Fsites-available\u002F",[16,53265,53266],{},"Confirm the previous stable behavior before making more changes.",[52,53268,4319],{"id":4318},[16,53270,53271,53272,53274],{},"If your team handles repeated timeout incidents, convert the manual checks into a small runbook script: collect Gunicorn and Nginx logs, capture CPU and memory state, verify endpoint latency, and compare current timeout settings. A standard Gunicorn ",[20,53273,1277],{}," unit and Nginx template also reduce configuration drift between servers.",[11,53276,1321],{"id":1320},[16,53278,53279,53280,53282],{},"Gunicorn kills workers that stay silent longer than the configured ",[20,53281,47977],{},". This protects the service from permanently stuck processes, but it also exposes bad request design quickly.",[16,53284,53285,53286,53288],{},"Raising ",[20,53287,47977],{}," can be correct for a known valid workload, but it often hides deeper issues:",[63,53290,53291,53294,53297,53300,53303],{},[66,53292,53293],{},"blocking database access",[66,53295,53296],{},"unbounded external API waits",[66,53298,53299],{},"too few workers",[66,53301,53302],{},"heavy file handling in Django",[66,53304,53305],{},"background work done inside a request",[16,53307,53308],{},"Concurrency choices matter too. With sync workers, a blocked request consumes the worker until completion or timeout. Threads can help when requests wait on I\u002FO, but they do not fix expensive CPU-bound code. If your app regularly needs long-running processing, the better design is usually a queue worker plus status polling or async completion.",[16,53310,53311],{},"Timeout tuning should also match the reverse proxy and the rest of the stack. If Nginx waits 60 seconds but Gunicorn kills workers at 30, clients may see inconsistent failures. If both are set very high, users may wait too long while the server remains unhealthy.",[11,53313,1337],{"id":1336},[52,53315,53317],{"id":53316},"timeout-only-during-deploys-or-immediately-after-restart","Timeout only during deploys or immediately after restart",[16,53319,53320],{},"Check for heavy imports, startup hooks, or app initialization making workers slow to become ready. Also verify migrations are not run inside web startup.",[52,53322,53324],{"id":53323},"timeout-only-on-admin-or-report-endpoints","Timeout only on admin or report endpoints",[16,53326,53327],{},"These often contain expensive queries, exports, or aggregation logic. Treat them as candidates for background jobs.",[52,53329,53331],{"id":53330},"timeout-only-under-traffic-spikes","Timeout only under traffic spikes",[16,53333,53334],{},"Look at worker count, memory limits, database pool exhaustion, and host CPU saturation. A healthy route can still time out under undersized capacity.",[52,53336,53338],{"id":53337},"timeout-in-docker-or-kubernetes","Timeout in Docker or Kubernetes",[16,53340,53341],{},"Check container memory limits, probe settings, and whether the platform is restarting the container before Gunicorn logs show a clear timeout. Do not assume Gunicorn is the first failure point.",[52,53343,53345],{"id":53344},"host-level-oom-kills","Host-level OOM kills",[16,53347,6168,53348,53350],{},[20,53349,52261],{}," shows OOM events, fix memory pressure first. Gunicorn may appear unstable when the kernel is killing workers underneath it.",[11,53352,1386],{"id":1385},[16,53354,32206,53355,211,53358,53360,53361,211,53363,53365,53366,211,53369,53371,53372,211],{},[1395,53356,53357],{"href":37877},"What Gunicorn Workers Do in Django Production",[20160,53359],{},"\nIf you need a baseline app server setup, read ",[1395,53362,4425],{"href":2985},[20160,53364],{},"\nFor long-running tasks, use ",[1395,53367,53368],{"href":6180},"How to Configure Celery for Long-Running Django Tasks",[20160,53370],{},"\nFor incident response workflow, see ",[1395,53373,53375],{"href":53374},"\u002Fdeploy\u002Fconfigure-nginx-for-django-production","How to Read Django, Gunicorn, and Nginx Logs During Production Incidents",[11,53377,1420],{"id":1419},[52,53379,32264,53381,53383],{"id":53380},"what-does-worker-timeout-mean-in-gunicorn-for-django",[20,53382,51704],{}," mean in Gunicorn for Django?",[16,53385,53386,53387,53389],{},"It means a Gunicorn worker stopped responding within the configured ",[20,53388,47977],{}," window, so Gunicorn killed and replaced it.",[52,53391,53393],{"id":53392},"should-i-just-increase-the-gunicorn-timeout-value","Should I just increase the Gunicorn timeout value?",[16,53395,53396],{},"Usually no. First confirm whether the request is slow because of app code, database queries, external APIs, worker starvation, or host pressure. Increase timeout only for known valid long-running requests.",[52,53398,53400],{"id":53399},"why-does-gunicorn-timeout-happen-only-on-one-django-endpoint","Why does Gunicorn timeout happen only on one Django endpoint?",[16,53402,53403],{},"That usually points to route-specific logic such as expensive queries, file generation, blocking API calls, or report-style processing in the request path.",[52,53405,53407],{"id":53406},"can-nginx-cause-what-looks-like-a-gunicorn-worker-timeout","Can Nginx cause what looks like a Gunicorn worker timeout?",[16,53409,53410,53411,53413],{},"Yes. Nginx can return upstream timeout errors even when Gunicorn itself is not logging ",[20,53412,51704],{},". Always compare both log sources before changing settings.",[52,53415,53417],{"id":53416},"when-should-long-running-django-work-move-to-a-background-job-system","When should long-running Django work move to a background job system?",[16,53419,53420],{},"When the task is not required to finish before returning the HTTP response, or when it regularly approaches app server timeout limits. Report generation, imports, exports, and third-party API workflows are common examples.",[1485,53422,53423],{},"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 .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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":53425},[53426,53427,53428,53448,53449,53456,53457],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":53429},[53430,53431,53441,53442,53443,53444,53445,53446,53447],{"id":51761,"depth":149,"text":51762},{"id":51898,"depth":149,"text":51899,"children":53432},[53433,53434,53435,53436,53437,53438,53439,53440],{"id":51902,"depth":162,"text":51903},{"id":52034,"depth":162,"text":52035},{"id":52113,"depth":162,"text":52114},{"id":52178,"depth":162,"text":52179},{"id":52227,"depth":162,"text":52228},{"id":52280,"depth":162,"text":52281},{"id":52287,"depth":162,"text":52288},{"id":52308,"depth":162,"text":52309},{"id":52317,"depth":149,"text":52318},{"id":52529,"depth":149,"text":52530},{"id":52794,"depth":149,"text":52795},{"id":52943,"depth":149,"text":52944},{"id":53051,"depth":149,"text":53052},{"id":53152,"depth":149,"text":53153},{"id":4318,"depth":149,"text":4319},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":53450},[53451,53452,53453,53454,53455],{"id":53316,"depth":149,"text":53317},{"id":53323,"depth":149,"text":53324},{"id":53330,"depth":149,"text":53331},{"id":53337,"depth":149,"text":53338},{"id":53344,"depth":149,"text":53345},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":53458},[53459,53461,53462,53463,53464],{"id":53380,"depth":149,"text":53460},"What does WORKER TIMEOUT mean in Gunicorn for Django?",{"id":53392,"depth":149,"text":53393},{"id":53399,"depth":149,"text":53400},{"id":53406,"depth":149,"text":53407},{"id":53416,"depth":149,"text":53417},"A Gunicorn worker timeout in Django production usually means a worker process stopped responding before Gunicorn’s timeout limit expired.",{},"\u002Ffix-gunicorn-worker-timeout-django",[4455,6333,10169],{"title":51691,"description":53465},[1557,14954],"fix-gunicorn-worker-timeout-django",[1557,14954],"a5mKUHJbgcQuK-FJeDjTYxYQbpRcxnsYvBbKvDrS6GQ",{"id":53475,"title":53476,"body":53477,"category":1541,"description":55333,"difficulty":1543,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":55334,"navigation":309,"path":55335,"priority":3094,"related":55336,"role":34162,"section":1554,"seo":55337,"stack":55338,"stem":55339,"tags":55340,"type":3104,"__hash__":55341},"articles\u002Fdjango-health-check-endpoint-guide.md","How to Build a Django Health Check Endpoint for Deployments",{"type":8,"value":53478,"toc":55296},[53479,53481,53487,53494,53501,53504,53524,53527,53529,53532,53550,53559,53572,53575,53577,53581,53584,53598,53601,53623,53626,53630,53633,53741,53744,53756,53760,53763,53860,53863,53874,53878,53903,53906,53919,53922,53926,53929,54227,54230,54234,54248,54250,54262,54264,54273,54276,54280,54283,54286,54647,54650,54653,54657,54660,54679,54682,54685,54688,54761,54767,54771,54775,54778,54799,54802,54808,54812,54815,54901,54911,54914,54936,54940,54947,54950,54961,54967,54971,54974,54978,54981,55002,55006,55009,55029,55031,55076,55091,55105,55107,55110,55122,55125,55128,55139,55141,55152,55154,55217,55219,55225,55233,55239,55241,55245,55248,55252,55261,55265,55273,55277,55280,55284,55293],[11,53480,14],{"id":13},[16,53482,53483,53484],{},"A production Django deployment needs a reliable way to answer a simple question: ",[1226,53485,53486],{},"is this instance safe to keep running and safe to receive traffic?",[16,53488,53489,53490,53493],{},"That is what a ",[1226,53491,53492],{},"Django health check endpoint"," is for. Deployment systems, load balancers, reverse proxies, container runtimes, and uptime monitors use health endpoints to decide whether a process is alive and whether a release is ready.",[16,53495,53496,53497,53500],{},"Using your homepage or ",[20,53498,53499],{},"\u002Fadmin\u002F"," as a health check is a poor substitute. Those routes may depend on templates, sessions, authentication, database-heavy queries, redirects, or middleware behavior that has nothing to do with basic process health. During a release, that can cause false failures or hide real ones.",[16,53502,53503],{},"A better pattern is:",[63,53505,53506,53515],{},[66,53507,53248,53508,53511,53512,53514],{},[1226,53509,53510],{},"liveness"," endpoint such as ",[20,53513,32941],{}," that only confirms the Django process is running",[66,53516,53248,53517,53511,53520,53523],{},[1226,53518,53519],{},"readiness",[20,53521,53522],{},"\u002Freadyz"," that confirms the app can serve real traffic, usually including critical dependencies like the primary database",[16,53525,53526],{},"This gives deployment tooling a stable signal without exposing secrets or adding unnecessary load.",[11,53528,30],{"id":29},[16,53530,53531],{},"Create two endpoints:",[63,53533,53534,53542],{},[66,53535,53536,53538,53539],{},[20,53537,32941],{}," for a fast ",[1226,53540,53541],{},"liveness probe",[66,53543,53544,53546,53547],{},[20,53545,53522],{}," for a slightly deeper ",[1226,53548,53549],{},"readiness check",[16,53551,53552,53553,53555,53556,53558],{},"Keep ",[20,53554,32941],{}," dependency-light and return HTTP 200 if the app process is healthy. Use ",[20,53557,53522],{}," to test only the dependencies required to serve requests, such as the default database. Return simple status codes:",[63,53560,53561,53566],{},[66,53562,53563,53565],{},[20,53564,42150],{}," when healthy",[66,53567,53568,53571],{},[20,53569,53570],{},"503 Service Unavailable"," when not ready",[16,53573,53574],{},"Then verify the endpoints through Django directly, through Gunicorn or Uvicorn, and through your reverse proxy.",[11,53576,43],{"id":42},[11,53578,53580],{"id":53579},"_1-define-what-each-endpoint-means","1. Define what each endpoint means",[16,53582,53583],{},"Before writing code, decide what each path should report.",[63,53585,53586,53592],{},[66,53587,53588,53591],{},[1226,53589,53590],{},"Liveness",": “Is the Django app process up and responding?”",[66,53593,53594,53597],{},[1226,53595,53596],{},"Readiness",": “Can this instance safely receive production traffic right now?”",[16,53599,53600],{},"In most Django deployments:",[63,53602,53603,53611,53620],{},[66,53604,53605,53607,53608,53610],{},[20,53606,32941],{}," should ",[1226,53609,7474],{}," depend on the database, Redis, or third-party APIs",[66,53612,53613,53615,53616,53619],{},[20,53614,53522],{}," should check the ",[1226,53617,53618],{},"database"," if your app cannot serve requests without it, but database reachability alone does not prove a new release is fully safe after schema or migration changes",[66,53621,53622],{},"optional services like Redis should only be included if traffic truly depends on them",[16,53624,53625],{},"Do not turn a health check into a full system audit. Health probes run often and should stay fast.",[11,53627,53629],{"id":53628},"_2-add-url-routes","2. Add URL routes",[16,53631,53632],{},"Create explicit routes in your project URL configuration.",[106,53634,53636],{"className":2369,"code":53635,"language":1114,"meta":111,"style":111},"# project\u002Furls.py\nfrom django.contrib import admin\nfrom django.urls import path\n\nfrom .views import healthz, readyz\n\nurlpatterns = [\n    path(\"admin\u002F\", admin.site.urls),\n    path(\"healthz\", healthz, name=\"healthz\"),\n    path(\"readyz\", readyz, name=\"readyz\"),\n]\n",[20,53637,53638,53642,53654,53664,53668,53680,53684,53692,53702,53719,53737],{"__ignoreMap":111},[115,53639,53640],{"class":117,"line":118},[115,53641,17233],{"class":3861},[115,53643,53644,53646,53649,53651],{"class":117,"line":136},[115,53645,5621],{"class":121},[115,53647,53648],{"class":125}," django.contrib ",[115,53650,5613],{"class":121},[115,53652,53653],{"class":125}," admin\n",[115,53655,53656,53658,53660,53662],{"class":117,"line":149},[115,53657,5621],{"class":121},[115,53659,17252],{"class":125},[115,53661,5613],{"class":121},[115,53663,17257],{"class":125},[115,53665,53666],{"class":117,"line":162},[115,53667,310],{"emptyLinePlaceholder":309},[115,53669,53670,53672,53675,53677],{"class":117,"line":175},[115,53671,5621],{"class":121},[115,53673,53674],{"class":125}," .views ",[115,53676,5613],{"class":121},[115,53678,53679],{"class":125}," healthz, readyz\n",[115,53681,53682],{"class":117,"line":350},[115,53683,310],{"emptyLinePlaceholder":309},[115,53685,53686,53688,53690],{"class":117,"line":365},[115,53687,17302],{"class":125},[115,53689,129],{"class":121},[115,53691,3540],{"class":125},[115,53693,53694,53696,53699],{"class":117,"line":380},[115,53695,17311],{"class":125},[115,53697,53698],{"class":132},"\"admin\u002F\"",[115,53700,53701],{"class":125},", admin.site.urls),\n",[115,53703,53704,53706,53708,53711,53713,53715,53717],{"class":117,"line":487},[115,53705,17311],{"class":125},[115,53707,17314],{"class":132},[115,53709,53710],{"class":125},", healthz, ",[115,53712,20820],{"class":5680},[115,53714,129],{"class":121},[115,53716,17314],{"class":132},[115,53718,10746],{"class":125},[115,53720,53721,53723,53726,53729,53731,53733,53735],{"class":117,"line":2095},[115,53722,17311],{"class":125},[115,53724,53725],{"class":132},"\"readyz\"",[115,53727,53728],{"class":125},", readyz, ",[115,53730,20820],{"class":5680},[115,53732,129],{"class":121},[115,53734,53725],{"class":132},[115,53736,10746],{"class":125},[115,53738,53739],{"class":117,"line":2104},[115,53740,2552],{"class":125},[16,53742,53743],{},"These paths should stay stable because infrastructure will depend on them.",[16,53745,53746,53747,3146,53749,53751,53752,53755],{},"A practical note on slashes: Django will match these routes as ",[20,53748,32941],{},[20,53750,53522],{},". Keep your probe path exactly consistent across Django, Nginx, Docker, and any load balancer. If your project relies on ",[20,53753,53754],{},"APPEND_SLASH",", do not assume a probe will follow redirects the way a browser does.",[11,53757,53759],{"id":53758},"_3-create-a-lightweight-liveness-view","3. Create a lightweight liveness view",[16,53761,53762],{},"The liveness endpoint should be cheap and predictable.",[106,53764,53766],{"className":2369,"code":53765,"language":1114,"meta":111,"style":111},"# project\u002Fviews.py\nfrom django.http import HttpResponseNotAllowed, JsonResponse\n\ndef healthz(request):\n    if request.method not in (\"GET\", \"HEAD\"):\n        return HttpResponseNotAllowed([\"GET\", \"HEAD\"])\n    return JsonResponse({\"status\": \"ok\"}, status=200)\n",[20,53767,53768,53773,53784,53788,53796,53820,53836],{"__ignoreMap":111},[115,53769,53770],{"class":117,"line":118},[115,53771,53772],{"class":3861},"# project\u002Fviews.py\n",[115,53774,53775,53777,53779,53781],{"class":117,"line":136},[115,53776,5621],{"class":121},[115,53778,17240],{"class":125},[115,53780,5613],{"class":121},[115,53782,53783],{"class":125}," HttpResponseNotAllowed, JsonResponse\n",[115,53785,53786],{"class":117,"line":149},[115,53787,310],{"emptyLinePlaceholder":309},[115,53789,53790,53792,53794],{"class":117,"line":162},[115,53791,8808],{"class":121},[115,53793,17268],{"class":262},[115,53795,17271],{"class":125},[115,53797,53798,53800,53803,53805,53808,53810,53813,53815,53818],{"class":117,"line":175},[115,53799,40975],{"class":121},[115,53801,53802],{"class":125}," request.method ",[115,53804,7474],{"class":121},[115,53806,53807],{"class":121}," in",[115,53809,2383],{"class":125},[115,53811,53812],{"class":132},"\"GET\"",[115,53814,1153],{"class":125},[115,53816,53817],{"class":132},"\"HEAD\"",[115,53819,37156],{"class":125},[115,53821,53822,53824,53827,53829,53831,53833],{"class":117,"line":350},[115,53823,6812],{"class":121},[115,53825,53826],{"class":125}," HttpResponseNotAllowed([",[115,53828,53812],{"class":132},[115,53830,1153],{"class":125},[115,53832,53817],{"class":132},[115,53834,53835],{"class":125},"])\n",[115,53837,53838,53840,53842,53844,53846,53848,53851,53854,53856,53858],{"class":117,"line":365},[115,53839,3822],{"class":121},[115,53841,18629],{"class":125},[115,53843,18632],{"class":132},[115,53845,2513],{"class":125},[115,53847,17281],{"class":132},[115,53849,53850],{"class":125},"}, ",[115,53852,53853],{"class":5680},"status",[115,53855,129],{"class":121},[115,53857,17741],{"class":202},[115,53859,2394],{"class":125},[16,53861,53862],{},"This is enough for a basic Django liveness probe. It confirms that:",[63,53864,53865,53868,53871],{},[66,53866,53867],{},"the app server is running",[66,53869,53870],{},"Django can route requests",[66,53872,53873],{},"the worker can generate a response",[52,53875,53877],{"id":53876},"verify-it-locally","Verify it locally",[106,53879,53881],{"className":108,"code":53880,"language":110,"meta":111,"style":111},"python manage.py runserver 127.0.0.1:8000\ncurl -i http:\u002F\u002F127.0.0.1:8000\u002Fhealthz\n",[20,53882,53883,53894],{"__ignoreMap":111},[115,53884,53885,53887,53889,53892],{"class":117,"line":118},[115,53886,1114],{"class":262},[115,53888,1117],{"class":132},[115,53890,53891],{"class":132}," runserver",[115,53893,32827],{"class":132},[115,53895,53896,53898,53900],{"class":117,"line":136},[115,53897,2764],{"class":262},[115,53899,14187],{"class":202},[115,53901,53902],{"class":132}," http:\u002F\u002F127.0.0.1:8000\u002Fhealthz\n",[16,53904,53905],{},"Expected result:",[63,53907,53908,53913],{},[66,53909,53910,53911],{},"HTTP status ",[20,53912,17741],{},[66,53914,53915,53916],{},"small JSON body such as ",[20,53917,53918],{},"{\"status\": \"ok\"}",[16,53920,53921],{},"If this fails, fix app startup or URL configuration before adding deeper checks.",[11,53923,53925],{"id":53924},"_4-add-a-readiness-endpoint-with-a-database-probe","4. Add a readiness endpoint with a database probe",[16,53927,53928],{},"A readiness check should fail when the app cannot serve normal traffic. For most Django applications, that means testing database connectivity.",[106,53930,53932],{"className":2369,"code":53931,"language":1114,"meta":111,"style":111},"# project\u002Fviews.py\nfrom django.db import connections\nfrom django.db.utils import DatabaseError\nfrom django.http import HttpResponseNotAllowed, JsonResponse\n\ndef healthz(request):\n    if request.method not in (\"GET\", \"HEAD\"):\n        return HttpResponseNotAllowed([\"GET\", \"HEAD\"])\n    return JsonResponse({\"status\": \"ok\"}, status=200)\n\ndef readyz(request):\n    if request.method not in (\"GET\", \"HEAD\"):\n        return HttpResponseNotAllowed([\"GET\", \"HEAD\"])\n\n    try:\n        with connections[\"default\"].cursor() as cursor:\n            cursor.execute(\"SELECT 1\")\n            cursor.fetchone()\n    except DatabaseError:\n        return JsonResponse(\n            {\"status\": \"error\", \"checks\": {\"database\": \"unavailable\"}},\n            status=503,\n        )\n\n    return JsonResponse({\"status\": \"ok\", \"checks\": {\"database\": \"ok\"}}, status=200)\n",[20,53933,53934,53938,53949,53961,53971,53975,53983,54003,54017,54039,54043,54052,54072,54086,54090,54096,54112,54122,54127,54135,54142,54172,54183,54188,54192],{"__ignoreMap":111},[115,53935,53936],{"class":117,"line":118},[115,53937,53772],{"class":3861},[115,53939,53940,53942,53944,53946],{"class":117,"line":136},[115,53941,5621],{"class":121},[115,53943,11218],{"class":125},[115,53945,5613],{"class":121},[115,53947,53948],{"class":125}," connections\n",[115,53950,53951,53953,53956,53958],{"class":117,"line":149},[115,53952,5621],{"class":121},[115,53954,53955],{"class":125}," django.db.utils ",[115,53957,5613],{"class":121},[115,53959,53960],{"class":125}," DatabaseError\n",[115,53962,53963,53965,53967,53969],{"class":117,"line":162},[115,53964,5621],{"class":121},[115,53966,17240],{"class":125},[115,53968,5613],{"class":121},[115,53970,53783],{"class":125},[115,53972,53973],{"class":117,"line":175},[115,53974,310],{"emptyLinePlaceholder":309},[115,53976,53977,53979,53981],{"class":117,"line":350},[115,53978,8808],{"class":121},[115,53980,17268],{"class":262},[115,53982,17271],{"class":125},[115,53984,53985,53987,53989,53991,53993,53995,53997,53999,54001],{"class":117,"line":365},[115,53986,40975],{"class":121},[115,53988,53802],{"class":125},[115,53990,7474],{"class":121},[115,53992,53807],{"class":121},[115,53994,2383],{"class":125},[115,53996,53812],{"class":132},[115,53998,1153],{"class":125},[115,54000,53817],{"class":132},[115,54002,37156],{"class":125},[115,54004,54005,54007,54009,54011,54013,54015],{"class":117,"line":380},[115,54006,6812],{"class":121},[115,54008,53826],{"class":125},[115,54010,53812],{"class":132},[115,54012,1153],{"class":125},[115,54014,53817],{"class":132},[115,54016,53835],{"class":125},[115,54018,54019,54021,54023,54025,54027,54029,54031,54033,54035,54037],{"class":117,"line":487},[115,54020,3822],{"class":121},[115,54022,18629],{"class":125},[115,54024,18632],{"class":132},[115,54026,2513],{"class":125},[115,54028,17281],{"class":132},[115,54030,53850],{"class":125},[115,54032,53853],{"class":5680},[115,54034,129],{"class":121},[115,54036,17741],{"class":202},[115,54038,2394],{"class":125},[115,54040,54041],{"class":117,"line":2095},[115,54042,310],{"emptyLinePlaceholder":309},[115,54044,54045,54047,54050],{"class":117,"line":2104},[115,54046,8808],{"class":121},[115,54048,54049],{"class":262}," readyz",[115,54051,17271],{"class":125},[115,54053,54054,54056,54058,54060,54062,54064,54066,54068,54070],{"class":117,"line":2113},[115,54055,40975],{"class":121},[115,54057,53802],{"class":125},[115,54059,7474],{"class":121},[115,54061,53807],{"class":121},[115,54063,2383],{"class":125},[115,54065,53812],{"class":132},[115,54067,1153],{"class":125},[115,54069,53817],{"class":132},[115,54071,37156],{"class":125},[115,54073,54074,54076,54078,54080,54082,54084],{"class":117,"line":2122},[115,54075,6812],{"class":121},[115,54077,53826],{"class":125},[115,54079,53812],{"class":132},[115,54081,1153],{"class":125},[115,54083,53817],{"class":132},[115,54085,53835],{"class":125},[115,54087,54088],{"class":117,"line":2131},[115,54089,310],{"emptyLinePlaceholder":309},[115,54091,54092,54094],{"class":117,"line":2136},[115,54093,51990],{"class":121},[115,54095,2498],{"class":125},[115,54097,54098,54100,54103,54105,54108,54110],{"class":117,"line":2142},[115,54099,27910],{"class":121},[115,54101,54102],{"class":125}," connections[",[115,54104,10847],{"class":132},[115,54106,54107],{"class":125},"].cursor() ",[115,54109,5719],{"class":121},[115,54111,11240],{"class":125},[115,54113,54114,54117,54120],{"class":117,"line":2273},[115,54115,54116],{"class":125},"            cursor.execute(",[115,54118,54119],{"class":132},"\"SELECT 1\"",[115,54121,2394],{"class":125},[115,54123,54124],{"class":117,"line":2282},[115,54125,54126],{"class":125},"            cursor.fetchone()\n",[115,54128,54129,54132],{"class":117,"line":2291},[115,54130,54131],{"class":121},"    except",[115,54133,54134],{"class":125}," DatabaseError:\n",[115,54136,54137,54139],{"class":117,"line":2299},[115,54138,6812],{"class":121},[115,54140,54141],{"class":125}," JsonResponse(\n",[115,54143,54144,54147,54149,54151,54154,54156,54159,54161,54164,54166,54169],{"class":117,"line":2307},[115,54145,54146],{"class":125},"            {",[115,54148,18632],{"class":132},[115,54150,2513],{"class":125},[115,54152,54153],{"class":132},"\"error\"",[115,54155,1153],{"class":125},[115,54157,54158],{"class":132},"\"checks\"",[115,54160,9131],{"class":125},[115,54162,54163],{"class":132},"\"database\"",[115,54165,2513],{"class":125},[115,54167,54168],{"class":132},"\"unavailable\"",[115,54170,54171],{"class":125},"}},\n",[115,54173,54174,54177,54179,54181],{"class":117,"line":2315},[115,54175,54176],{"class":5680},"            status",[115,54178,129],{"class":121},[115,54180,38062],{"class":202},[115,54182,3354],{"class":125},[115,54184,54185],{"class":117,"line":2320},[115,54186,54187],{"class":125},"        )\n",[115,54189,54190],{"class":117,"line":7083},[115,54191,310],{"emptyLinePlaceholder":309},[115,54193,54194,54196,54198,54200,54202,54204,54206,54208,54210,54212,54214,54216,54219,54221,54223,54225],{"class":117,"line":7090},[115,54195,3822],{"class":121},[115,54197,18629],{"class":125},[115,54199,18632],{"class":132},[115,54201,2513],{"class":125},[115,54203,17281],{"class":132},[115,54205,1153],{"class":125},[115,54207,54158],{"class":132},[115,54209,9131],{"class":125},[115,54211,54163],{"class":132},[115,54213,2513],{"class":125},[115,54215,17281],{"class":132},[115,54217,54218],{"class":125},"}}, ",[115,54220,53853],{"class":5680},[115,54222,129],{"class":121},[115,54224,17741],{"class":202},[115,54226,2394],{"class":125},[16,54228,54229],{},"This uses Django’s configured default database connection and performs a minimal query.",[52,54231,54233],{"id":54232},"verify-readiness","Verify readiness",[106,54235,54237],{"className":108,"code":54236,"language":110,"meta":111,"style":111},"curl -i http:\u002F\u002F127.0.0.1:8000\u002Freadyz\n",[20,54238,54239],{"__ignoreMap":111},[115,54240,54241,54243,54245],{"class":117,"line":118},[115,54242,2764],{"class":262},[115,54244,14187],{"class":202},[115,54246,54247],{"class":132}," http:\u002F\u002F127.0.0.1:8000\u002Freadyz\n",[16,54249,53905],{},[63,54251,54252,54257],{},[66,54253,54254,54256],{},[20,54255,42150],{}," when the database is reachable",[66,54258,54259,54261],{},[20,54260,53570],{}," when it is not",[52,54263,4956],{"id":4955},[16,54265,54266,54267,54269,54270,54272],{},"If a new release returns ",[20,54268,38062],{}," from ",[20,54271,53522],{}," after deployment, do not shift traffic to it. Roll back to the previous known-good version or restore the broken dependency before retrying.",[16,54274,54275],{},"If the failure started after a schema change, check whether migrations were skipped, partially applied, or incompatible with the new code. Database reachability is only one part of release safety.",[11,54277,54279],{"id":54278},"_5-optionally-check-redis-or-cache","5. Optionally check Redis or cache",[16,54281,54282],{},"Only add cache or Redis to readiness if your app truly depends on it for request handling. Prefer a low-cost connectivity check, and avoid unnecessary writes on every probe.",[16,54284,54285],{},"Example using Django’s cache backend:",[106,54287,54289],{"className":2369,"code":54288,"language":1114,"meta":111,"style":111},"# project\u002Fviews.py\nfrom django.core.cache import cache\nfrom django.db import connections\nfrom django.db.utils import DatabaseError\nfrom django.http import HttpResponseNotAllowed, JsonResponse\n\ndef readyz(request):\n    if request.method not in (\"GET\", \"HEAD\"):\n        return HttpResponseNotAllowed([\"GET\", \"HEAD\"])\n\n    checks = {}\n\n    try:\n        with connections[\"default\"].cursor() as cursor:\n            cursor.execute(\"SELECT 1\")\n            cursor.fetchone()\n        checks[\"database\"] = \"ok\"\n    except DatabaseError:\n        checks[\"database\"] = \"unavailable\"\n        return JsonResponse({\"status\": \"error\", \"checks\": checks}, status=503)\n\n    try:\n        cache.set(\"healthcheck\", \"ok\", timeout=5)\n        if cache.get(\"healthcheck\") != \"ok\":\n            raise RuntimeError(\"cache readback failed\")\n        checks[\"cache\"] = \"ok\"\n    except Exception:\n        checks[\"cache\"] = \"unavailable\"\n        return JsonResponse({\"status\": \"error\", \"checks\": checks}, status=503)\n\n    return JsonResponse({\"status\": \"ok\", \"checks\": checks}, status=200)\n",[20,54290,54291,54295,54307,54317,54327,54337,54341,54349,54369,54383,54387,54397,54401,54407,54421,54429,54433,54446,54452,54465,54492,54496,54502,54524,54543,54557,54570,54579,54591,54617,54621],{"__ignoreMap":111},[115,54292,54293],{"class":117,"line":118},[115,54294,53772],{"class":3861},[115,54296,54297,54299,54302,54304],{"class":117,"line":136},[115,54298,5621],{"class":121},[115,54300,54301],{"class":125}," django.core.cache ",[115,54303,5613],{"class":121},[115,54305,54306],{"class":125}," cache\n",[115,54308,54309,54311,54313,54315],{"class":117,"line":149},[115,54310,5621],{"class":121},[115,54312,11218],{"class":125},[115,54314,5613],{"class":121},[115,54316,53948],{"class":125},[115,54318,54319,54321,54323,54325],{"class":117,"line":162},[115,54320,5621],{"class":121},[115,54322,53955],{"class":125},[115,54324,5613],{"class":121},[115,54326,53960],{"class":125},[115,54328,54329,54331,54333,54335],{"class":117,"line":175},[115,54330,5621],{"class":121},[115,54332,17240],{"class":125},[115,54334,5613],{"class":121},[115,54336,53783],{"class":125},[115,54338,54339],{"class":117,"line":350},[115,54340,310],{"emptyLinePlaceholder":309},[115,54342,54343,54345,54347],{"class":117,"line":365},[115,54344,8808],{"class":121},[115,54346,54049],{"class":262},[115,54348,17271],{"class":125},[115,54350,54351,54353,54355,54357,54359,54361,54363,54365,54367],{"class":117,"line":380},[115,54352,40975],{"class":121},[115,54354,53802],{"class":125},[115,54356,7474],{"class":121},[115,54358,53807],{"class":121},[115,54360,2383],{"class":125},[115,54362,53812],{"class":132},[115,54364,1153],{"class":125},[115,54366,53817],{"class":132},[115,54368,37156],{"class":125},[115,54370,54371,54373,54375,54377,54379,54381],{"class":117,"line":487},[115,54372,6812],{"class":121},[115,54374,53826],{"class":125},[115,54376,53812],{"class":132},[115,54378,1153],{"class":125},[115,54380,53817],{"class":132},[115,54382,53835],{"class":125},[115,54384,54385],{"class":117,"line":2095},[115,54386,310],{"emptyLinePlaceholder":309},[115,54388,54389,54392,54394],{"class":117,"line":2104},[115,54390,54391],{"class":125},"    checks ",[115,54393,129],{"class":121},[115,54395,54396],{"class":125}," {}\n",[115,54398,54399],{"class":117,"line":2113},[115,54400,310],{"emptyLinePlaceholder":309},[115,54402,54403,54405],{"class":117,"line":2122},[115,54404,51990],{"class":121},[115,54406,2498],{"class":125},[115,54408,54409,54411,54413,54415,54417,54419],{"class":117,"line":2131},[115,54410,27910],{"class":121},[115,54412,54102],{"class":125},[115,54414,10847],{"class":132},[115,54416,54107],{"class":125},[115,54418,5719],{"class":121},[115,54420,11240],{"class":125},[115,54422,54423,54425,54427],{"class":117,"line":2136},[115,54424,54116],{"class":125},[115,54426,54119],{"class":132},[115,54428,2394],{"class":125},[115,54430,54431],{"class":117,"line":2142},[115,54432,54126],{"class":125},[115,54434,54435,54438,54440,54442,54444],{"class":117,"line":2273},[115,54436,54437],{"class":125},"        checks[",[115,54439,54163],{"class":132},[115,54441,10861],{"class":125},[115,54443,129],{"class":121},[115,54445,8831],{"class":132},[115,54447,54448,54450],{"class":117,"line":2282},[115,54449,54131],{"class":121},[115,54451,54134],{"class":125},[115,54453,54454,54456,54458,54460,54462],{"class":117,"line":2291},[115,54455,54437],{"class":125},[115,54457,54163],{"class":132},[115,54459,10861],{"class":125},[115,54461,129],{"class":121},[115,54463,54464],{"class":132}," \"unavailable\"\n",[115,54466,54467,54469,54471,54473,54475,54477,54479,54481,54484,54486,54488,54490],{"class":117,"line":2299},[115,54468,6812],{"class":121},[115,54470,18629],{"class":125},[115,54472,18632],{"class":132},[115,54474,2513],{"class":125},[115,54476,54153],{"class":132},[115,54478,1153],{"class":125},[115,54480,54158],{"class":132},[115,54482,54483],{"class":125},": checks}, ",[115,54485,53853],{"class":5680},[115,54487,129],{"class":121},[115,54489,38062],{"class":202},[115,54491,2394],{"class":125},[115,54493,54494],{"class":117,"line":2307},[115,54495,310],{"emptyLinePlaceholder":309},[115,54497,54498,54500],{"class":117,"line":2315},[115,54499,51990],{"class":121},[115,54501,2498],{"class":125},[115,54503,54504,54507,54510,54512,54514,54516,54518,54520,54522],{"class":117,"line":2320},[115,54505,54506],{"class":125},"        cache.set(",[115,54508,54509],{"class":132},"\"healthcheck\"",[115,54511,1153],{"class":125},[115,54513,17281],{"class":132},[115,54515,1153],{"class":125},[115,54517,47977],{"class":5680},[115,54519,129],{"class":121},[115,54521,14025],{"class":202},[115,54523,2394],{"class":125},[115,54525,54526,54528,54531,54533,54535,54538,54541],{"class":117,"line":7083},[115,54527,37216],{"class":121},[115,54529,54530],{"class":125}," cache.get(",[115,54532,54509],{"class":132},[115,54534,18281],{"class":125},[115,54536,54537],{"class":121},"!=",[115,54539,54540],{"class":132}," \"ok\"",[115,54542,2498],{"class":125},[115,54544,54545,54548,54550,54552,54555],{"class":117,"line":7090},[115,54546,54547],{"class":121},"            raise",[115,54549,37619],{"class":202},[115,54551,37145],{"class":125},[115,54553,54554],{"class":132},"\"cache readback failed\"",[115,54556,2394],{"class":125},[115,54558,54559,54561,54564,54566,54568],{"class":117,"line":7097},[115,54560,54437],{"class":125},[115,54562,54563],{"class":132},"\"cache\"",[115,54565,10861],{"class":125},[115,54567,129],{"class":121},[115,54569,8831],{"class":132},[115,54571,54572,54574,54577],{"class":117,"line":7108},[115,54573,54131],{"class":121},[115,54575,54576],{"class":202}," Exception",[115,54578,2498],{"class":125},[115,54580,54581,54583,54585,54587,54589],{"class":117,"line":7113},[115,54582,54437],{"class":125},[115,54584,54563],{"class":132},[115,54586,10861],{"class":125},[115,54588,129],{"class":121},[115,54590,54464],{"class":132},[115,54592,54593,54595,54597,54599,54601,54603,54605,54607,54609,54611,54613,54615],{"class":117,"line":16535},[115,54594,6812],{"class":121},[115,54596,18629],{"class":125},[115,54598,18632],{"class":132},[115,54600,2513],{"class":125},[115,54602,54153],{"class":132},[115,54604,1153],{"class":125},[115,54606,54158],{"class":132},[115,54608,54483],{"class":125},[115,54610,53853],{"class":5680},[115,54612,129],{"class":121},[115,54614,38062],{"class":202},[115,54616,2394],{"class":125},[115,54618,54619],{"class":117,"line":16544},[115,54620,310],{"emptyLinePlaceholder":309},[115,54622,54623,54625,54627,54629,54631,54633,54635,54637,54639,54641,54643,54645],{"class":117,"line":16549},[115,54624,3822],{"class":121},[115,54626,18629],{"class":125},[115,54628,18632],{"class":132},[115,54630,2513],{"class":125},[115,54632,17281],{"class":132},[115,54634,1153],{"class":125},[115,54636,54158],{"class":132},[115,54638,54483],{"class":125},[115,54640,53853],{"class":5680},[115,54642,129],{"class":121},[115,54644,17741],{"class":202},[115,54646,2394],{"class":125},[16,54648,54649],{},"Note: this write\u002Fread example is simple but can add avoidable probe traffic. Use it only when cache availability is critical and probe frequency is controlled.",[16,54651,54652],{},"Do not call external APIs from readiness probes.",[11,54654,54656],{"id":54655},"_6-keep-the-response-minimal-and-safe","6. Keep the response minimal and safe",[16,54658,54659],{},"A health endpoint should not expose:",[63,54661,54662,54665,54668,54671,54673,54676],{},[66,54663,54664],{},"Django version",[66,54666,54667],{},"environment names",[66,54669,54670],{},"hostnames",[66,54672,2675],{},[66,54674,54675],{},"stack traces",[66,54677,54678],{},"internal exception messages",[16,54680,54681],{},"Good response bodies are small and boring. The status code matters more than detailed output.",[16,54683,54684],{},"If you need to restrict access, prefer doing it at the network or proxy layer instead of adding authentication that can break load balancer probes.",[16,54686,54687],{},"Example Nginx restriction:",[106,54689,54691],{"className":2154,"code":54690,"language":2156,"meta":111,"style":111},"location = \u002Freadyz {\n    allow 10.0.0.0\u002F8;\n    allow 127.0.0.1;\n    deny all;\n\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n",[20,54692,54693,54704,54712,54720,54729,54733,54739,54745,54751,54757],{"__ignoreMap":111},[115,54694,54695,54697,54699,54702],{"class":117,"line":118},[115,54696,7128],{"class":121},[115,54698,2380],{"class":121},[115,54700,54701],{"class":12955}," \u002Freadyz ",[115,54703,2220],{"class":125},[115,54705,54706,54709],{"class":117,"line":136},[115,54707,54708],{"class":121},"    allow ",[115,54710,54711],{"class":125},"10.0.0.0\u002F8;\n",[115,54713,54714,54716,54718],{"class":117,"line":149},[115,54715,54708],{"class":121},[115,54717,46371],{"class":202},[115,54719,3811],{"class":125},[115,54721,54722,54725,54727],{"class":117,"line":162},[115,54723,54724],{"class":121},"    deny ",[115,54726,12966],{"class":202},[115,54728,3811],{"class":125},[115,54730,54731],{"class":117,"line":175},[115,54732,310],{"emptyLinePlaceholder":309},[115,54734,54735,54737],{"class":117,"line":350},[115,54736,7137],{"class":121},[115,54738,3748],{"class":125},[115,54740,54741,54743],{"class":117,"line":365},[115,54742,7144],{"class":121},[115,54744,2288],{"class":125},[115,54746,54747,54749],{"class":117,"line":380},[115,54748,7144],{"class":121},[115,54750,2312],{"class":125},[115,54752,54753,54755],{"class":117,"line":487},[115,54754,7144],{"class":121},[115,54756,2304],{"class":125},[115,54758,54759],{"class":117,"line":2095},[115,54760,2323],{"class":125},[16,54762,54763,54764,54766],{},"If a public uptime monitor needs access, either allow its source ranges or expose only the shallow ",[20,54765,32941],{}," endpoint publicly.",[11,54768,54770],{"id":54769},"_7-wire-it-into-deployment-infrastructure","7. Wire it into deployment infrastructure",[52,54772,54774],{"id":54773},"docker-health-check","Docker health check",[16,54776,54777],{},"If you run Django in a container, use the liveness endpoint for the container health check.",[106,54779,54781],{"className":16832,"code":54780,"language":16834,"meta":111,"style":111},"HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \\\n  CMD curl --fail http:\u002F\u002F127.0.0.1:8000\u002Fhealthz || exit 1\n",[20,54782,54783,54791],{"__ignoreMap":111},[115,54784,54785,54788],{"class":117,"line":118},[115,54786,54787],{"class":121},"HEALTHCHECK",[115,54789,54790],{"class":125}," --interval=30s --timeout=5s --start-period=20s --retries=3 \\\n",[115,54792,54793,54796],{"class":117,"line":136},[115,54794,54795],{"class":121},"  CMD",[115,54797,54798],{"class":125}," curl --fail http:\u002F\u002F127.0.0.1:8000\u002Fhealthz || exit 1\n",[16,54800,54801],{},"This keeps the container probe shallow and avoids tying container restarts to database availability.",[16,54803,54804,54805,54807],{},"This example requires ",[20,54806,2764],{}," in the image. Many slim images do not include it, so either install it explicitly or use a different probe command that exists in the container.",[52,54809,54811],{"id":54810},"reverse-proxy-pass-through","Reverse proxy pass-through",[16,54813,54814],{},"A normal Nginx proxy block is enough:",[106,54816,54818],{"className":2154,"code":54817,"language":2156,"meta":111,"style":111},"location = \u002Fhealthz {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n\nlocation = \u002Freadyz {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n",[20,54819,54820,54831,54837,54843,54849,54855,54859,54863,54873,54879,54885,54891,54897],{"__ignoreMap":111},[115,54821,54822,54824,54826,54829],{"class":117,"line":118},[115,54823,7128],{"class":121},[115,54825,2380],{"class":121},[115,54827,54828],{"class":12955}," \u002Fhealthz ",[115,54830,2220],{"class":125},[115,54832,54833,54835],{"class":117,"line":136},[115,54834,7137],{"class":121},[115,54836,3748],{"class":125},[115,54838,54839,54841],{"class":117,"line":149},[115,54840,7144],{"class":121},[115,54842,2288],{"class":125},[115,54844,54845,54847],{"class":117,"line":162},[115,54846,7144],{"class":121},[115,54848,2312],{"class":125},[115,54850,54851,54853],{"class":117,"line":175},[115,54852,7144],{"class":121},[115,54854,2304],{"class":125},[115,54856,54857],{"class":117,"line":350},[115,54858,2323],{"class":125},[115,54860,54861],{"class":117,"line":365},[115,54862,310],{"emptyLinePlaceholder":309},[115,54864,54865,54867,54869,54871],{"class":117,"line":380},[115,54866,7128],{"class":121},[115,54868,2380],{"class":121},[115,54870,54701],{"class":12955},[115,54872,2220],{"class":125},[115,54874,54875,54877],{"class":117,"line":487},[115,54876,7137],{"class":121},[115,54878,3748],{"class":125},[115,54880,54881,54883],{"class":117,"line":2095},[115,54882,7144],{"class":121},[115,54884,2288],{"class":125},[115,54886,54887,54889],{"class":117,"line":2104},[115,54888,7144],{"class":121},[115,54890,2312],{"class":125},[115,54892,54893,54895],{"class":117,"line":2113},[115,54894,7144],{"class":121},[115,54896,2304],{"class":125},[115,54898,54899],{"class":117,"line":2122},[115,54900,2323],{"class":125},[16,54902,54903,54904,54906,54907,54910],{},"If your app server listens on a Unix socket instead of ",[20,54905,20396],{},", point ",[20,54908,54909],{},"proxy_pass"," at the actual upstream you use in production. Do not copy a TCP localhost example unchanged if your Gunicorn or Uvicorn setup is socket-based.",[16,54912,54913],{},"Then test through Nginx:",[106,54915,54917],{"className":108,"code":54916,"language":110,"meta":111,"style":111},"curl -i https:\u002F\u002Fexample.com\u002Fhealthz\ncurl -i https:\u002F\u002Fexample.com\u002Freadyz\n",[20,54918,54919,54927],{"__ignoreMap":111},[115,54920,54921,54923,54925],{"class":117,"line":118},[115,54922,2764],{"class":262},[115,54924,14187],{"class":202},[115,54926,2780],{"class":132},[115,54928,54929,54931,54933],{"class":117,"line":136},[115,54930,2764],{"class":262},[115,54932,14187],{"class":202},[115,54934,54935],{"class":132}," https:\u002F\u002Fexample.com\u002Freadyz\n",[52,54937,54939],{"id":54938},"load-balancers-and-deployment-systems","Load balancers and deployment systems",[16,54941,55,54942,54944,54945,211],{},[20,54943,53522],{}," for traffic cutover when possible. A load balancer should consider the target healthy only when it gets HTTP 200 from ",[20,54946,53522],{},[16,54948,54949],{},"That helps catch:",[63,54951,54952,54955,54958],{},[66,54953,54954],{},"broken database credentials",[66,54956,54957],{},"unreachable database hosts",[66,54959,54960],{},"startup states where the process is running but not actually usable",[16,54962,54963,54964,54966],{},"But do not treat ",[20,54965,53522],{}," as full release verification. It can show green while a newly deployed build still has application-level issues, pending migrations, or incompatible schema assumptions.",[11,54968,54970],{"id":54969},"_8-verify-during-deployment","8. Verify during deployment",[16,54972,54973],{},"Run checks at multiple layers.",[52,54975,54977],{"id":54976},"app-level-verification","App-level verification",[16,54979,54980],{},"Check Django or Gunicorn\u002FUvicorn directly first:",[106,54982,54984],{"className":108,"code":54983,"language":110,"meta":111,"style":111},"curl -i http:\u002F\u002F127.0.0.1:8000\u002Fhealthz\ncurl -i http:\u002F\u002F127.0.0.1:8000\u002Freadyz\n",[20,54985,54986,54994],{"__ignoreMap":111},[115,54987,54988,54990,54992],{"class":117,"line":118},[115,54989,2764],{"class":262},[115,54991,14187],{"class":202},[115,54993,53902],{"class":132},[115,54995,54996,54998,55000],{"class":117,"line":136},[115,54997,2764],{"class":262},[115,54999,14187],{"class":202},[115,55001,54247],{"class":132},[52,55003,55005],{"id":55004},"proxy-level-verification","Proxy-level verification",[16,55007,55008],{},"Then check through the public hostname and reverse proxy:",[106,55010,55011],{"className":108,"code":54916,"language":110,"meta":111,"style":111},[20,55012,55013,55021],{"__ignoreMap":111},[115,55014,55015,55017,55019],{"class":117,"line":118},[115,55016,2764],{"class":262},[115,55018,14187],{"class":202},[115,55020,2780],{"class":132},[115,55022,55023,55025,55027],{"class":117,"line":136},[115,55024,2764],{"class":262},[115,55026,14187],{"class":202},[115,55028,54935],{"class":132},[16,55030,43213],{},[106,55032,55034],{"className":108,"code":55033,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,55035,55036,55052,55064],{"__ignoreMap":111},[115,55037,55038,55040,55042,55044,55046,55048,55050],{"class":117,"line":118},[115,55039,2001],{"class":262},[115,55041,5030],{"class":132},[115,55043,2788],{"class":202},[115,55045,2791],{"class":132},[115,55047,2794],{"class":202},[115,55049,2797],{"class":202},[115,55051,2800],{"class":202},[115,55053,55054,55056,55058,55060,55062],{"class":117,"line":136},[115,55055,2001],{"class":262},[115,55057,13188],{"class":132},[115,55059,2794],{"class":202},[115,55061,2797],{"class":202},[115,55063,30354],{"class":132},[115,55065,55066,55068,55070,55072,55074],{"class":117,"line":149},[115,55067,2001],{"class":262},[115,55069,13188],{"class":132},[115,55071,2794],{"class":202},[115,55073,2797],{"class":202},[115,55075,13195],{"class":132},[16,55077,55078,55079,55081,55082,55084,55085,55087,55088,55090],{},"Validate failure behavior too. If the database is intentionally made unavailable, ",[20,55080,53522],{}," should fail with ",[20,55083,38062],{}," while ",[20,55086,32941],{}," may still return ",[20,55089,17741],{},". That distinction is useful: it tells you the app process is alive but not ready for traffic.",[16,55092,55093,55094,55096,55097,55099,55100,55102,55103,211],{},"If the endpoints work on ",[20,55095,46371],{}," but fail through the public hostname, check your reverse proxy, TLS termination, firewall rules, upstream configuration, and ",[20,55098,2719],{},". A missing host in ",[20,55101,2719],{}," can cause Django to reject requests with ",[20,55104,46086],{},[11,55106,1321],{"id":1320},[16,55108,55109],{},"This setup works because it separates two different operational signals.",[63,55111,55112,55117],{},[66,55113,55114,55116],{},[1226,55115,53590],{}," protects against dead or wedged processes.",[66,55118,55119,55121],{},[1226,55120,53596],{}," protects users from being routed to an instance that cannot actually serve requests.",[16,55123,55124],{},"That separation matters in Django deployments with Gunicorn, Uvicorn, Nginx, containers, or load balancers. A process can be alive while migrations are incomplete, database credentials are broken, or the primary database is down.",[16,55126,55127],{},"Not every health endpoint should hit the database. If your orchestrator restarts containers based on failed liveness checks, a database-backed liveness endpoint can create restart loops during a database outage. That is why the database belongs in readiness for most deployments, not liveness.",[16,55129,55130,55131,55134,55135,55138],{},"Also remember that ",[1226,55132,55133],{},"readiness is not the same as full release correctness",". A database ",[20,55136,55137],{},"SELECT 1"," proves connectivity, not that your new code is compatible with the current schema, that background workers are healthy, or that static assets were deployed correctly.",[52,55140,50697],{"id":50696},[16,55142,55143,55144,3146,55146,55148,55149,55151],{},"If you add the same ",[20,55145,32941],{},[20,55147,53522],{}," pattern to every Django project, this is a good candidate for a shared module or deployment template. The repeated parts are the URL include, the standard JSON responses, the proxy rules, and the post-deploy ",[20,55150,2764],{}," verification step. Automating those reduces release mistakes without changing the health-check design.",[11,55153,11443],{"id":11442},[63,55155,55156,55162,55179,55187,55192,55198,55203],{},[66,55157,55158,55161],{},[1226,55159,55160],{},"Background workers:"," Celery or RQ worker health usually needs separate checks. Do not assume the web app health endpoint covers worker availability.",[66,55163,55164,55167,55168,55171,55172,3146,55175,55178],{},[1226,55165,55166],{},"Subpath deployments:"," If your app lives under ",[20,55169,55170],{},"\u002Fapi\u002F",", expose ",[20,55173,55174],{},"\u002Fapi\u002Fhealthz",[20,55176,55177],{},"\u002Fapi\u002Freadyz",", but keep the same semantics.",[66,55180,55181,55183,55184,55186],{},[1226,55182,2926],{}," If a release requires a schema change, readiness may fail until migrations complete. It can also return ",[20,55185,17741],{}," even when the app is not truly safe after a bad migration sequence. Plan deployment ordering carefully and keep a rollback path.",[66,55188,55189,55191],{},[1226,55190,2944],{}," Health endpoints must stay fast. Avoid expensive ORM queries, external HTTP requests, or checks that can block worker threads.",[66,55193,55194,55197],{},[1226,55195,55196],{},"Probe frequency:"," Readiness checks can be called often. Keep them cheap so they do not create avoidable database or cache load across many instances.",[66,55199,55200,55202],{},[1226,55201,41808],{}," A health check does not verify static or media delivery. Test those separately if your release changes proxy or storage configuration.",[66,55204,55205,55208,55209,55212,55213,55216],{},[1226,55206,55207],{},"Proxy confusion:"," If ",[20,55210,55211],{},"localhost:8000\u002Freadyz"," works but ",[20,55214,55215],{},"https:\u002F\u002Fexample.com\u002Freadyz"," fails, the issue is usually Nginx, TLS termination, firewall rules, or upstream routing rather than Django itself.",[11,55218,1386],{"id":1385},[16,55220,55221,55222,211],{},"For the concepts behind this pattern, see ",[1395,55223,55224],{"href":35630},"What Is the Difference Between Liveness and Readiness Checks in Django Deployments",[16,55226,55227,55228,3146,55231,211],{},"For related implementation guidance, see ",[1395,55229,55230],{"href":2985},"How to Deploy Django with Gunicorn and Nginx",[1395,55232,39061],{"href":1409},[16,55234,55235,55236,211],{},"If your release succeeds but traffic still does not shift, see ",[1395,55237,55238],{"href":35630},"Why Your Django Deployment Passes Build but Fails Health Checks",[11,55240,1420],{"id":1419},[52,55242,55244],{"id":55243},"what-is-the-difference-between-a-django-liveness-endpoint-and-a-readiness-endpoint","What is the difference between a Django liveness endpoint and a readiness endpoint?",[16,55246,55247],{},"A liveness endpoint shows that the Django process is up and responding. A readiness endpoint shows that the instance can safely serve production traffic, usually including critical dependency checks like the primary database.",[52,55249,55251],{"id":55250},"should-a-django-health-check-endpoint-query-the-database-every-time","Should a Django health check endpoint query the database every time?",[16,55253,55254,55255,55257,55258,55260],{},"Usually only the ",[1226,55256,53519],{}," endpoint should. The ",[1226,55259,53510],{}," endpoint should stay independent of the database so a temporary database failure does not make the process look dead.",[52,55262,55264],{"id":55263},"what-status-code-should-a-readiness-endpoint-return","What status code should a readiness endpoint return?",[16,55266,55,55267,55269,55270,55272],{},[20,55268,42150],{}," when ready and ",[20,55271,53570],{}," when not ready. Those are simple and widely understood by deployment systems, reverse proxies, and load balancers.",[52,55274,55276],{"id":55275},"should-health-check-endpoints-be-public","Should health check endpoints be public?",[16,55278,55279],{},"Not always. If possible, restrict access at the network or reverse-proxy layer. If an endpoint must be public, keep it minimal and avoid exposing unnecessary internal details.",[52,55281,55283],{"id":55282},"can-the-same-endpoint-be-used-for-docker-and-a-load-balancer","Can the same endpoint be used for Docker and a load balancer?",[16,55285,55286,55287,55289,55290,55292],{},"You can, but it is usually better not to. Use ",[20,55288,32941],{}," for container liveness and ",[20,55291,53522],{}," for traffic routing decisions. That separation avoids restart loops and makes failures easier to diagnose.",[1485,55294,55295],{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}",{"title":111,"searchDepth":149,"depth":149,"links":55297},[55298,55299,55300,55301,55302,55303,55306,55310,55311,55312,55317,55321,55324,55325,55326],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":53579,"depth":136,"text":53580},{"id":53628,"depth":136,"text":53629},{"id":53758,"depth":136,"text":53759,"children":55304},[55305],{"id":53876,"depth":149,"text":53877},{"id":53924,"depth":136,"text":53925,"children":55307},[55308,55309],{"id":54232,"depth":149,"text":54233},{"id":4955,"depth":149,"text":4956},{"id":54278,"depth":136,"text":54279},{"id":54655,"depth":136,"text":54656},{"id":54769,"depth":136,"text":54770,"children":55313},[55314,55315,55316],{"id":54773,"depth":149,"text":54774},{"id":54810,"depth":149,"text":54811},{"id":54938,"depth":149,"text":54939},{"id":54969,"depth":136,"text":54970,"children":55318},[55319,55320],{"id":54976,"depth":149,"text":54977},{"id":55004,"depth":149,"text":55005},{"id":1320,"depth":136,"text":1321,"children":55322},[55323],{"id":50696,"depth":149,"text":50697},{"id":11442,"depth":136,"text":11443},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":55327},[55328,55329,55330,55331,55332],{"id":55243,"depth":149,"text":55244},{"id":55250,"depth":149,"text":55251},{"id":55263,"depth":149,"text":55264},{"id":55275,"depth":149,"text":55276},{"id":55282,"depth":149,"text":55283},"A production Django deployment needs a reliable way to answer a simple question: is this instance safe to keep running and safe to receive traffic?",{},"\u002Fdjango-health-check-endpoint-guide",[1409,40223,35631],{"title":53476,"description":55333},[1557],"django-health-check-endpoint-guide",[1557],"iAOW6mzxumknguPyBukOE-PlRNegJG45BrXXsL4tCgI",{"id":55343,"title":55344,"body":55345,"category":3088,"description":57189,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":57190,"navigation":309,"path":57191,"priority":57192,"related":57193,"role":1553,"section":3098,"seo":57194,"stack":57195,"stem":57196,"tags":57197,"type":1561,"__hash__":57198},"articles\u002Fconfigure-nginx-for-django-production.md","How to Configure Nginx for Django in Production",{"type":8,"value":55346,"toc":57165},[55347,55349,55352,55355,55375,55378,55380,55383,55432,55434,55438,55441,55444,55475,55478,55501,55504,55529,55532,55585,55587,55604,55607,55609,55613,55616,55631,55634,55884,55887,55910,55912,55933,55935,55948,55951,55967,55969,55973,55976,55990,55993,56007,56010,56024,56027,56030,56046,56048,56075,56078,56080,56093,56099,56101,56105,56108,56111,56429,56431,56453,56456,56501,56506,56509,56511,56546,56549,56551,56555,56557,56574,56577,56592,56595,56607,56609,56623,56626,56628,56670,56673,56675,56679,56681,56699,56702,56721,56724,56758,56761,56781,56784,56800,56802,56805,56808,56816,56819,56830,56833,56844,56846,56857,56859,56921,56923,56928,56931,56946,56952,56954,56958,56961,56965,56973,56996,56999,57003,57005,57034,57037,57057,57061,57064,57083,57087,57090,57125,57128,57160,57163],[11,55348,14],{"id":13},[16,55350,55351],{},"A production Django stack should not expose Gunicorn or Uvicorn directly to the internet. You need a reverse proxy in front of the app server to handle TLS, serve static files efficiently, forward requests safely, and give you a stable place to apply request limits and logging.",[16,55353,55354],{},"The real problem is not just “make Nginx work.” It is configuring Nginx so Django behaves correctly in production:",[63,55356,55357,55360,55363,55366,55369,55372],{},[66,55358,55359],{},"requests reach the app server reliably",[66,55361,55362],{},"static files are served directly by Nginx",[66,55364,55365],{},"HTTPS is enforced",[66,55367,55368],{},"Django sees the correct host and scheme, and receives client IP headers from Nginx",[66,55370,55371],{},"file permissions and socket access do not break requests",[66,55373,55374],{},"you can test and roll back changes safely",[16,55376,55377],{},"A bad Nginx configuration often causes 502 errors, broken static assets, CSRF failures, insecure cookies, or a site that reloads cleanly but fails under real traffic.",[11,55379,30],{"id":29},[16,55381,55382],{},"For a safe Django production Nginx setup, use this pattern:",[63,55384,55385,55390,55393,55399,55404,55407,55419,55424],{},[66,55386,55387,55388,6411],{},"run Django with Gunicorn or Uvicorn bound to ",[20,55389,20396],{},[66,55391,55392],{},"put Nginx in front as the public entry point",[66,55394,55395,55396,55398],{},"let Nginx serve ",[20,55397,11729],{}," directly",[66,55400,11726,55401,55403],{},[20,55402,13085],{}," from Nginx only if media files are stored on the same server",[66,55405,55406],{},"terminate TLS at Nginx",[66,55408,55409,55410,1153,55412,1153,55415,20346,55417],{},"pass ",[20,55411,3648],{},[20,55413,55414],{},"X-Real-IP",[20,55416,7305],{},[20,55418,3203],{},[66,55420,43291,55421,55423],{},[20,55422,7611],{}," before reload",[66,55425,55426,55427,1153,55429,55431],{},"verify Django settings such as ",[20,55428,2719],{},[20,55430,2725],{},", and secure proxy handling",[11,55433,43],{"id":42},[52,55435,55437],{"id":55436},"step-1-confirm-the-django-app-server-is-working-first","Step 1 — Confirm the Django app server is working first",[16,55439,55440],{},"Before putting Nginx in front, make sure Gunicorn or Uvicorn already works locally.",[16,55442,55443],{},"If using Gunicorn via systemd:",[106,55445,55447],{"className":108,"code":55446,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn\nsudo journalctl -u gunicorn -n 50 --no-pager\n",[20,55448,55449,55459],{"__ignoreMap":111},[115,55450,55451,55453,55455,55457],{"class":117,"line":118},[115,55452,2001],{"class":262},[115,55454,3480],{"class":132},[115,55456,1984],{"class":132},[115,55458,1987],{"class":132},[115,55460,55461,55463,55465,55467,55469,55471,55473],{"class":117,"line":136},[115,55462,2001],{"class":262},[115,55464,5030],{"class":132},[115,55466,2788],{"class":202},[115,55468,2791],{"class":132},[115,55470,2794],{"class":202},[115,55472,15523],{"class":202},[115,55474,2800],{"class":202},[16,55476,55477],{},"If using TCP on port 8000:",[106,55479,55481],{"className":108,"code":55480,"language":110,"meta":111,"style":111},"ss -ltnp | grep 8000\ncurl http:\u002F\u002F127.0.0.1:8000\u002F\n",[20,55482,55483,55495],{"__ignoreMap":111},[115,55484,55485,55487,55489,55491,55493],{"class":117,"line":118},[115,55486,6472],{"class":262},[115,55488,52214],{"class":202},[115,55490,579],{"class":121},[115,55492,4838],{"class":262},[115,55494,23864],{"class":202},[115,55496,55497,55499],{"class":117,"line":136},[115,55498,2764],{"class":262},[115,55500,3950],{"class":132},[16,55502,55503],{},"If using a Unix socket:",[106,55505,55507],{"className":108,"code":55506,"language":110,"meta":111,"style":111},"ls -l \u002Frun\u002Fgunicorn.sock\ncurl --unix-socket \u002Frun\u002Fgunicorn.sock http:\u002F\u002Flocalhost\u002F\n",[20,55508,55509,55518],{"__ignoreMap":111},[115,55510,55511,55513,55515],{"class":117,"line":118},[115,55512,532],{"class":262},[115,55514,14881],{"class":202},[115,55516,55517],{"class":132}," \u002Frun\u002Fgunicorn.sock\n",[115,55519,55520,55522,55524,55527],{"class":117,"line":136},[115,55521,2764],{"class":262},[115,55523,13366],{"class":202},[115,55525,55526],{"class":132}," \u002Frun\u002Fgunicorn.sock",[115,55528,13372],{"class":132},[16,55530,55531],{},"Also confirm Django production settings match the domain:",[106,55533,55535],{"className":2369,"code":55534,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\"example.com\", \"www.example.com\"]\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\", \"https:\u002F\u002Fwww.example.com\"]\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n",[20,55536,55537,55553,55569],{"__ignoreMap":111},[115,55538,55539,55541,55543,55545,55547,55549,55551],{"class":117,"line":118},[115,55540,2719],{"class":202},[115,55542,2380],{"class":121},[115,55544,7493],{"class":125},[115,55546,7496],{"class":132},[115,55548,1153],{"class":125},[115,55550,7501],{"class":132},[115,55552,2552],{"class":125},[115,55554,55555,55557,55559,55561,55563,55565,55567],{"class":117,"line":136},[115,55556,2725],{"class":202},[115,55558,2380],{"class":121},[115,55560,7493],{"class":125},[115,55562,15110],{"class":132},[115,55564,1153],{"class":125},[115,55566,15115],{"class":132},[115,55568,2552],{"class":125},[115,55570,55571,55573,55575,55577,55579,55581,55583],{"class":117,"line":149},[115,55572,2377],{"class":202},[115,55574,2380],{"class":121},[115,55576,2383],{"class":125},[115,55578,2386],{"class":132},[115,55580,1153],{"class":125},[115,55582,2391],{"class":132},[115,55584,2394],{"class":125},[16,55586,17389],{},[63,55588,55589,55592,55596,55601],{},[66,55590,55591],{},"the app responds directly on localhost or socket",[66,55593,55594],{},[20,55595,2707],{},[66,55597,55598,55600],{},[20,55599,2719],{}," contains the production domain",[66,55602,55603],{},"CSRF trusted origins include the HTTPS URL",[16,55605,55606],{},"If this step fails, fix the app server first. Nginx will not fix an unhealthy Django process.",[23099,55608],{},[52,55610,55612],{"id":55611},"step-2-create-the-nginx-server-block-for-django","Step 2 — Create the Nginx server block for Django",[16,55614,55615],{},"Choose Unix socket or TCP upstream:",[63,55617,55618,55625],{},[66,55619,55620,55621,55624],{},"use a ",[1226,55622,55623],{},"Unix socket"," when Nginx and Gunicorn run on the same host",[66,55626,44234,55627,55630],{},[1226,55628,55629],{},"TCP"," when the app server runs in a container or on another host",[16,55632,55633],{},"Example site config for Gunicorn over a Unix socket:",[106,55635,55637],{"className":2154,"code":55636,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server unix:\u002Frun\u002Fgunicorn.sock;\n}\n\nserver {\n    listen 80;\n    server_name example.com www.example.com;\n\n    access_log \u002Fvar\u002Flog\u002Fnginx\u002Fexample.access.log;\n    error_log \u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log;\n\n    client_max_body_size 10M;\n    server_tokens off;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n        access_log off;\n        expires 7d;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fdjango_app;\n        proxy_http_version 1.1;\n\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        proxy_connect_timeout 5s;\n        proxy_send_timeout 60s;\n        proxy_read_timeout 60s;\n    }\n}\n",[20,55638,55639,55647,55654,55658,55662,55668,55676,55682,55686,55694,55702,55706,55714,55723,55727,55735,55741,55749,55758,55768,55772,55776,55784,55790,55794,55798,55806,55812,55820,55824,55830,55836,55842,55848,55852,55860,55868,55876,55880],{"__ignoreMap":111},[115,55640,55641,55643,55645],{"class":117,"line":118},[115,55642,52563],{"class":121},[115,55644,52566],{"class":262},[115,55646,2220],{"class":125},[115,55648,55649,55651],{"class":117,"line":136},[115,55650,52573],{"class":121},[115,55652,55653],{"class":125}," unix:\u002Frun\u002Fgunicorn.sock;\n",[115,55655,55656],{"class":117,"line":149},[115,55657,2323],{"class":125},[115,55659,55660],{"class":117,"line":162},[115,55661,310],{"emptyLinePlaceholder":309},[115,55663,55664,55666],{"class":117,"line":175},[115,55665,2163],{"class":121},[115,55667,2166],{"class":125},[115,55669,55670,55672,55674],{"class":117,"line":350},[115,55671,2171],{"class":121},[115,55673,3808],{"class":202},[115,55675,3811],{"class":125},[115,55677,55678,55680],{"class":117,"line":365},[115,55679,2182],{"class":121},[115,55681,3713],{"class":125},[115,55683,55684],{"class":117,"line":380},[115,55685,310],{"emptyLinePlaceholder":309},[115,55687,55688,55691],{"class":117,"line":487},[115,55689,55690],{"class":121},"    access_log ",[115,55692,55693],{"class":125},"\u002Fvar\u002Flog\u002Fnginx\u002Fexample.access.log;\n",[115,55695,55696,55699],{"class":117,"line":2095},[115,55697,55698],{"class":121},"    error_log ",[115,55700,55701],{"class":125},"\u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log;\n",[115,55703,55704],{"class":117,"line":2104},[115,55705,310],{"emptyLinePlaceholder":309},[115,55707,55708,55710,55712],{"class":117,"line":2113},[115,55709,6987],{"class":121},[115,55711,12827],{"class":202},[115,55713,3811],{"class":125},[115,55715,55716,55719,55721],{"class":117,"line":2122},[115,55717,55718],{"class":121},"    server_tokens ",[115,55720,7103],{"class":202},[115,55722,3811],{"class":125},[115,55724,55725],{"class":117,"line":2131},[115,55726,310],{"emptyLinePlaceholder":309},[115,55728,55729,55731,55733],{"class":117,"line":2136},[115,55730,2214],{"class":121},[115,55732,2217],{"class":262},[115,55734,2220],{"class":125},[115,55736,55737,55739],{"class":117,"line":2142},[115,55738,2225],{"class":121},[115,55740,2228],{"class":125},[115,55742,55743,55745,55747],{"class":117,"line":2273},[115,55744,42575],{"class":121},[115,55746,7103],{"class":202},[115,55748,3811],{"class":125},[115,55750,55751,55753,55756],{"class":117,"line":2282},[115,55752,42584],{"class":121},[115,55754,55755],{"class":202},"7d",[115,55757,3811],{"class":125},[115,55759,55760,55762,55764,55766],{"class":117,"line":2291},[115,55761,42594],{"class":121},[115,55763,42597],{"class":125},[115,55765,42600],{"class":132},[115,55767,3811],{"class":125},[115,55769,55770],{"class":117,"line":2299},[115,55771,2233],{"class":125},[115,55773,55774],{"class":117,"line":2307},[115,55775,310],{"emptyLinePlaceholder":309},[115,55777,55778,55780,55782],{"class":117,"line":2315},[115,55779,2214],{"class":121},[115,55781,2244],{"class":262},[115,55783,2220],{"class":125},[115,55785,55786,55788],{"class":117,"line":2320},[115,55787,2225],{"class":121},[115,55789,2253],{"class":125},[115,55791,55792],{"class":117,"line":7083},[115,55793,2233],{"class":125},[115,55795,55796],{"class":117,"line":7090},[115,55797,310],{"emptyLinePlaceholder":309},[115,55799,55800,55802,55804],{"class":117,"line":7097},[115,55801,2214],{"class":121},[115,55803,2268],{"class":262},[115,55805,2220],{"class":125},[115,55807,55808,55810],{"class":117,"line":7108},[115,55809,2276],{"class":121},[115,55811,52685],{"class":125},[115,55813,55814,55816,55818],{"class":117,"line":7113},[115,55815,12876],{"class":121},[115,55817,12879],{"class":202},[115,55819,3811],{"class":125},[115,55821,55822],{"class":117,"line":16535},[115,55823,310],{"emptyLinePlaceholder":309},[115,55825,55826,55828],{"class":117,"line":16544},[115,55827,2285],{"class":121},[115,55829,2288],{"class":125},[115,55831,55832,55834],{"class":117,"line":16549},[115,55833,2285],{"class":121},[115,55835,3767],{"class":125},[115,55837,55838,55840],{"class":117,"line":16555},[115,55839,2285],{"class":121},[115,55841,2312],{"class":125},[115,55843,55844,55846],{"class":117,"line":16564},[115,55845,2285],{"class":121},[115,55847,2304],{"class":125},[115,55849,55850],{"class":117,"line":16573},[115,55851,310],{"emptyLinePlaceholder":309},[115,55853,55854,55856,55858],{"class":117,"line":16582},[115,55855,12925],{"class":121},[115,55857,52722],{"class":202},[115,55859,3811],{"class":125},[115,55861,55862,55864,55866],{"class":117,"line":16587},[115,55863,52738],{"class":121},[115,55865,52731],{"class":202},[115,55867,3811],{"class":125},[115,55869,55870,55872,55874],{"class":117,"line":16596},[115,55871,12916],{"class":121},[115,55873,52731],{"class":202},[115,55875,3811],{"class":125},[115,55877,55878],{"class":117,"line":16609},[115,55879,2233],{"class":125},[115,55881,55882],{"class":117,"line":16614},[115,55883,2323],{"class":125},[16,55885,55886],{},"If using TCP instead:",[106,55888,55890],{"className":2154,"code":55889,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server 127.0.0.1:8000;\n}\n",[20,55891,55892,55900,55906],{"__ignoreMap":111},[115,55893,55894,55896,55898],{"class":117,"line":118},[115,55895,52563],{"class":121},[115,55897,52566],{"class":262},[115,55899,2220],{"class":125},[115,55901,55902,55904],{"class":117,"line":136},[115,55903,52573],{"class":121},[115,55905,52576],{"class":125},[115,55907,55908],{"class":117,"line":149},[115,55909,2323],{"class":125},[16,55911,8508],{},[63,55913,55914,55924,55930],{},[66,55915,44234,55916,55918,55919,3146,55921,55923],{},[20,55917,13001],{}," for ",[20,55920,11729],{},[20,55922,13085],{}," when mapping URL paths to filesystem directories",[66,55925,55926,55927],{},"the trailing slash matters: ",[20,55928,55929],{},"location \u002Fstatic\u002F { alias \u002Fpath\u002Fto\u002Fstaticfiles\u002F; }",[66,55931,55932],{},"do not point Nginx at your project source tree unless that is actually where collected files live",[16,55934,17389],{},[63,55936,55937,55942,55945],{},[66,55938,55939,55941],{},[20,55940,49803],{}," matches your real domain",[66,55943,55944],{},"static and media paths exist on disk",[66,55946,55947],{},"socket path matches the Gunicorn service config",[16,55949,55950],{},"Rollback note: keep a backup before editing.",[106,55952,55954],{"className":108,"code":55953,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp.bak\n",[20,55955,55956],{"__ignoreMap":111},[115,55957,55958,55960,55962,55964],{"class":117,"line":118},[115,55959,2001],{"class":262},[115,55961,6516],{"class":132},[115,55963,15709],{"class":132},[115,55965,55966],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp.bak\n",[23099,55968],{},[52,55970,55972],{"id":55971},"step-3-serve-static-and-media-files-correctly","Step 3 — Serve static and media files correctly",[16,55974,55975],{},"Collect static files before expecting Nginx to serve them:",[106,55977,55978],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,55979,55980],{"__ignoreMap":111},[115,55981,55982,55984,55986,55988],{"class":117,"line":118},[115,55983,1114],{"class":262},[115,55985,1117],{"class":132},[115,55987,1838],{"class":132},[115,55989,1841],{"class":202},[16,55991,55992],{},"Check the target directory:",[106,55994,55996],{"className":108,"code":55995,"language":110,"meta":111,"style":111},"ls -lah \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F\n",[20,55997,55998],{"__ignoreMap":111},[115,55999,56000,56002,56004],{"class":117,"line":118},[115,56001,532],{"class":262},[115,56003,12216],{"class":202},[115,56005,56006],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F\n",[16,56008,56009],{},"If media is stored locally, verify that directory too:",[106,56011,56013],{"className":108,"code":56012,"language":110,"meta":111,"style":111},"ls -lah \u002Fsrv\u002Fmyapp\u002Fmedia\u002F\n",[20,56014,56015],{"__ignoreMap":111},[115,56016,56017,56019,56021],{"class":117,"line":118},[115,56018,532],{"class":262},[115,56020,12216],{"class":202},[115,56022,56023],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fmedia\u002F\n",[16,56025,56026],{},"Nginx must be able to read those files, and if using a socket, it must be able to connect to it. Depending on your distro and service user, that may require adjusting ownership or group membership carefully.",[16,56028,56029],{},"A common pattern is:",[63,56031,56032,56038,56043],{},[66,56033,56034,56035],{},"Gunicorn writes the socket under ",[20,56036,56037],{},"\u002Frun\u002F",[66,56039,56040,56041],{},"Nginx runs as ",[20,56042,12660],{},[66,56044,56045],{},"the socket group allows Nginx access",[16,56047,4195],{},[106,56049,56051],{"className":108,"code":56050,"language":110,"meta":111,"style":111},"ps aux | egrep 'nginx: worker|nginx: master'\nnamei -l \u002Frun\u002Fgunicorn.sock\n",[20,56052,56053,56067],{"__ignoreMap":111},[115,56054,56055,56057,56059,56061,56064],{"class":117,"line":118},[115,56056,4830],{"class":262},[115,56058,4833],{"class":132},[115,56060,579],{"class":121},[115,56062,56063],{"class":262}," egrep",[115,56065,56066],{"class":132}," 'nginx: worker|nginx: master'\n",[115,56068,56069,56071,56073],{"class":117,"line":136},[115,56070,31618],{"class":262},[115,56072,14881],{"class":202},[115,56074,55517],{"class":132},[16,56076,56077],{},"Do not use Django to serve static files in normal production traffic. Let Nginx handle them directly.",[16,56079,17389],{},[106,56081,56083],{"className":108,"code":56082,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[20,56084,56085],{"__ignoreMap":111},[115,56086,56087,56089,56091],{"class":117,"line":118},[115,56088,2764],{"class":262},[115,56090,2767],{"class":202},[115,56092,13155],{"class":132},[16,56094,56095,56096,56098],{},"You want a ",[20,56097,42150],{}," from Nginx, not a redirect to Django.",[23099,56100],{},[52,56102,56104],{"id":56103},"step-4-enable-https-and-secure-the-nginx-configuration","Step 4 — Enable HTTPS and secure the Nginx configuration",[16,56106,56107],{},"Add an HTTP-to-HTTPS redirect and define a complete TLS site config.",[16,56109,56110],{},"Example with a Unix socket upstream:",[106,56112,56114],{"className":2154,"code":56113,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server unix:\u002Frun\u002Fgunicorn.sock;\n}\n\nserver {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fexample.com\u002Fprivkey.pem;\n\n    access_log \u002Fvar\u002Flog\u002Fnginx\u002Fexample.access.log;\n    error_log \u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log;\n\n    client_max_body_size 10M;\n    server_tokens off;\n\n    add_header X-Content-Type-Options nosniff always;\n    add_header X-Frame-Options SAMEORIGIN always;\n    add_header Referrer-Policy strict-origin-when-cross-origin always;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n        access_log off;\n        expires 7d;\n        add_header Cache-Control \"public\";\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fdjango_app;\n        proxy_http_version 1.1;\n\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        proxy_connect_timeout 5s;\n        proxy_send_timeout 60s;\n        proxy_read_timeout 60s;\n    }\n}\n",[20,56115,56116,56124,56130,56134,56138,56144,56152,56158,56166,56170,56174,56180,56188,56194,56198,56204,56210,56214,56220,56226,56230,56238,56246,56250,56256,56263,56269,56273,56281,56287,56295,56303,56313,56317,56321,56329,56335,56339,56343,56351,56357,56365,56369,56375,56381,56387,56393,56397,56405,56413,56421,56425],{"__ignoreMap":111},[115,56117,56118,56120,56122],{"class":117,"line":118},[115,56119,52563],{"class":121},[115,56121,52566],{"class":262},[115,56123,2220],{"class":125},[115,56125,56126,56128],{"class":117,"line":136},[115,56127,52573],{"class":121},[115,56129,55653],{"class":125},[115,56131,56132],{"class":117,"line":149},[115,56133,2323],{"class":125},[115,56135,56136],{"class":117,"line":162},[115,56137,310],{"emptyLinePlaceholder":309},[115,56139,56140,56142],{"class":117,"line":175},[115,56141,2163],{"class":121},[115,56143,2166],{"class":125},[115,56145,56146,56148,56150],{"class":117,"line":350},[115,56147,2171],{"class":121},[115,56149,3808],{"class":202},[115,56151,3811],{"class":125},[115,56153,56154,56156],{"class":117,"line":365},[115,56155,2182],{"class":121},[115,56157,3713],{"class":125},[115,56159,56160,56162,56164],{"class":117,"line":380},[115,56161,3822],{"class":121},[115,56163,3825],{"class":202},[115,56165,3828],{"class":125},[115,56167,56168],{"class":117,"line":487},[115,56169,2323],{"class":125},[115,56171,56172],{"class":117,"line":2095},[115,56173,310],{"emptyLinePlaceholder":309},[115,56175,56176,56178],{"class":117,"line":2104},[115,56177,2163],{"class":121},[115,56179,2166],{"class":125},[115,56181,56182,56184,56186],{"class":117,"line":2113},[115,56183,2171],{"class":121},[115,56185,2174],{"class":202},[115,56187,2177],{"class":125},[115,56189,56190,56192],{"class":117,"line":2122},[115,56191,2182],{"class":121},[115,56193,3713],{"class":125},[115,56195,56196],{"class":117,"line":2131},[115,56197,310],{"emptyLinePlaceholder":309},[115,56199,56200,56202],{"class":117,"line":2136},[115,56201,2194],{"class":121},[115,56203,2197],{"class":125},[115,56205,56206,56208],{"class":117,"line":2142},[115,56207,2202],{"class":121},[115,56209,2205],{"class":125},[115,56211,56212],{"class":117,"line":2273},[115,56213,310],{"emptyLinePlaceholder":309},[115,56215,56216,56218],{"class":117,"line":2282},[115,56217,55690],{"class":121},[115,56219,55693],{"class":125},[115,56221,56222,56224],{"class":117,"line":2291},[115,56223,55698],{"class":121},[115,56225,55701],{"class":125},[115,56227,56228],{"class":117,"line":2299},[115,56229,310],{"emptyLinePlaceholder":309},[115,56231,56232,56234,56236],{"class":117,"line":2307},[115,56233,6987],{"class":121},[115,56235,12827],{"class":202},[115,56237,3811],{"class":125},[115,56239,56240,56242,56244],{"class":117,"line":2315},[115,56241,55718],{"class":121},[115,56243,7103],{"class":202},[115,56245,3811],{"class":125},[115,56247,56248],{"class":117,"line":2320},[115,56249,310],{"emptyLinePlaceholder":309},[115,56251,56252,56254],{"class":117,"line":7083},[115,56253,34726],{"class":121},[115,56255,34729],{"class":125},[115,56257,56258,56260],{"class":117,"line":7090},[115,56259,34726],{"class":121},[115,56261,56262],{"class":125},"X-Frame-Options SAMEORIGIN always;\n",[115,56264,56265,56267],{"class":117,"line":7097},[115,56266,34726],{"class":121},[115,56268,34743],{"class":125},[115,56270,56271],{"class":117,"line":7108},[115,56272,310],{"emptyLinePlaceholder":309},[115,56274,56275,56277,56279],{"class":117,"line":7113},[115,56276,2214],{"class":121},[115,56278,2217],{"class":262},[115,56280,2220],{"class":125},[115,56282,56283,56285],{"class":117,"line":16535},[115,56284,2225],{"class":121},[115,56286,2228],{"class":125},[115,56288,56289,56291,56293],{"class":117,"line":16544},[115,56290,42575],{"class":121},[115,56292,7103],{"class":202},[115,56294,3811],{"class":125},[115,56296,56297,56299,56301],{"class":117,"line":16549},[115,56298,42584],{"class":121},[115,56300,55755],{"class":202},[115,56302,3811],{"class":125},[115,56304,56305,56307,56309,56311],{"class":117,"line":16555},[115,56306,42594],{"class":121},[115,56308,42597],{"class":125},[115,56310,42600],{"class":132},[115,56312,3811],{"class":125},[115,56314,56315],{"class":117,"line":16564},[115,56316,2233],{"class":125},[115,56318,56319],{"class":117,"line":16573},[115,56320,310],{"emptyLinePlaceholder":309},[115,56322,56323,56325,56327],{"class":117,"line":16582},[115,56324,2214],{"class":121},[115,56326,2244],{"class":262},[115,56328,2220],{"class":125},[115,56330,56331,56333],{"class":117,"line":16587},[115,56332,2225],{"class":121},[115,56334,2253],{"class":125},[115,56336,56337],{"class":117,"line":16596},[115,56338,2233],{"class":125},[115,56340,56341],{"class":117,"line":16609},[115,56342,310],{"emptyLinePlaceholder":309},[115,56344,56345,56347,56349],{"class":117,"line":16614},[115,56346,2214],{"class":121},[115,56348,2268],{"class":262},[115,56350,2220],{"class":125},[115,56352,56353,56355],{"class":117,"line":16624},[115,56354,2276],{"class":121},[115,56356,52685],{"class":125},[115,56358,56359,56361,56363],{"class":117,"line":16632},[115,56360,12876],{"class":121},[115,56362,12879],{"class":202},[115,56364,3811],{"class":125},[115,56366,56367],{"class":117,"line":16640},[115,56368,310],{"emptyLinePlaceholder":309},[115,56370,56371,56373],{"class":117,"line":16646},[115,56372,2285],{"class":121},[115,56374,2288],{"class":125},[115,56376,56377,56379],{"class":117,"line":16651},[115,56378,2285],{"class":121},[115,56380,3767],{"class":125},[115,56382,56383,56385],{"class":117,"line":16656},[115,56384,2285],{"class":121},[115,56386,2312],{"class":125},[115,56388,56389,56391],{"class":117,"line":16666},[115,56390,2285],{"class":121},[115,56392,2304],{"class":125},[115,56394,56395],{"class":117,"line":16674},[115,56396,310],{"emptyLinePlaceholder":309},[115,56398,56399,56401,56403],{"class":117,"line":16687},[115,56400,12925],{"class":121},[115,56402,52722],{"class":202},[115,56404,3811],{"class":125},[115,56406,56407,56409,56411],{"class":117,"line":16692},[115,56408,52738],{"class":121},[115,56410,52731],{"class":202},[115,56412,3811],{"class":125},[115,56414,56415,56417,56419],{"class":117,"line":16697},[115,56416,12916],{"class":121},[115,56418,52731],{"class":202},[115,56420,3811],{"class":125},[115,56422,56423],{"class":117,"line":16702},[115,56424,2233],{"class":125},[115,56426,56427],{"class":117,"line":16711},[115,56428,2323],{"class":125},[16,56430,55886],{},[106,56432,56433],{"className":2154,"code":55889,"language":2156,"meta":111,"style":111},[20,56434,56435,56443,56449],{"__ignoreMap":111},[115,56436,56437,56439,56441],{"class":117,"line":118},[115,56438,52563],{"class":121},[115,56440,52566],{"class":262},[115,56442,2220],{"class":125},[115,56444,56445,56447],{"class":117,"line":136},[115,56446,52573],{"class":121},[115,56448,52576],{"class":125},[115,56450,56451],{"class":117,"line":149},[115,56452,2323],{"class":125},[16,56454,56455],{},"In Django, make sure secure requests are recognized:",[106,56457,56459],{"className":2369,"code":56458,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_SSL_REDIRECT = True\n",[20,56460,56461,56477,56485,56493],{"__ignoreMap":111},[115,56462,56463,56465,56467,56469,56471,56473,56475],{"class":117,"line":118},[115,56464,2377],{"class":202},[115,56466,2380],{"class":121},[115,56468,2383],{"class":125},[115,56470,2386],{"class":132},[115,56472,1153],{"class":125},[115,56474,2391],{"class":132},[115,56476,2394],{"class":125},[115,56478,56479,56481,56483],{"class":117,"line":136},[115,56480,2417],{"class":202},[115,56482,2380],{"class":121},[115,56484,2412],{"class":202},[115,56486,56487,56489,56491],{"class":117,"line":149},[115,56488,2426],{"class":202},[115,56490,2380],{"class":121},[115,56492,2412],{"class":202},[115,56494,56495,56497,56499],{"class":117,"line":162},[115,56496,2407],{"class":202},[115,56498,2380],{"class":121},[115,56500,2412],{"class":202},[16,56502,55,56503,56505],{},[20,56504,13296],{}," only when your proxy setup is correct and all public traffic should be HTTPS.",[16,56507,56508],{},"Be cautious with HSTS. Only enable it after HTTPS works consistently across the site and subdomains.",[16,56510,17389],{},[106,56512,56514],{"className":108,"code":56513,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\u002F\ncurl -I https:\u002F\u002Fexample.com\u002F\ncurl -I -H 'Host: example.com' http:\u002F\u002F127.0.0.1:8000\u002F\n",[20,56515,56516,56525,56533],{"__ignoreMap":111},[115,56517,56518,56520,56522],{"class":117,"line":118},[115,56519,2764],{"class":262},[115,56521,2767],{"class":202},[115,56523,56524],{"class":132}," http:\u002F\u002Fexample.com\u002F\n",[115,56526,56527,56529,56531],{"class":117,"line":136},[115,56528,2764],{"class":262},[115,56530,2767],{"class":202},[115,56532,32984],{"class":132},[115,56534,56535,56537,56539,56541,56544],{"class":117,"line":149},[115,56536,2764],{"class":262},[115,56538,2767],{"class":202},[115,56540,3939],{"class":202},[115,56542,56543],{"class":132}," 'Host: example.com'",[115,56545,3950],{"class":132},[16,56547,56548],{},"You should see HTTP redirect to HTTPS, and HTTPS should return a valid response. If secure cookies or CSRF behavior is inconsistent, verify in Django that requests behind Nginx are treated as HTTPS.",[23099,56550],{},[52,56552,56554],{"id":56553},"step-5-enable-the-site-and-test-safely","Step 5 — Enable the site and test safely",[16,56556,15693],{},[106,56558,56560],{"className":108,"code":56559,"language":110,"meta":111,"style":111},"sudo ln -s \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp \u002Fetc\u002Fnginx\u002Fsites-enabled\u002F\n",[20,56561,56562],{"__ignoreMap":111},[115,56563,56564,56566,56568,56570,56572],{"class":117,"line":118},[115,56565,2001],{"class":262},[115,56567,13105],{"class":132},[115,56569,549],{"class":202},[115,56571,15709],{"class":132},[115,56573,15712],{"class":132},[16,56575,56576],{},"If the default site is enabled and may conflict, disable it:",[106,56578,56580],{"className":108,"code":56579,"language":110,"meta":111,"style":111},"sudo rm -f \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fdefault\n",[20,56581,56582],{"__ignoreMap":111},[115,56583,56584,56586,56588,56590],{"class":117,"line":118},[115,56585,2001],{"class":262},[115,56587,15719],{"class":132},[115,56589,2777],{"class":202},[115,56591,15724],{"class":132},[16,56593,56594],{},"Test configuration before reload:",[106,56596,56597],{"className":108,"code":4271,"language":110,"meta":111,"style":111},[20,56598,56599],{"__ignoreMap":111},[115,56600,56601,56603,56605],{"class":117,"line":118},[115,56602,2001],{"class":262},[115,56604,3906],{"class":132},[115,56606,4282],{"class":202},[16,56608,50301],{},[106,56610,56611],{"className":108,"code":50304,"language":110,"meta":111,"style":111},[20,56612,56613],{"__ignoreMap":111},[115,56614,56615,56617,56619,56621],{"class":117,"line":118},[115,56616,2001],{"class":262},[115,56618,3480],{"class":132},[115,56620,3919],{"class":132},[115,56622,1996],{"class":132},[16,56624,56625],{},"If the test fails, do not reload. Fix the syntax first.",[16,56627,17389],{},[106,56629,56631],{"className":108,"code":56630,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002F\ncurl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log\nsudo journalctl -u gunicorn -f\n",[20,56632,56633,56641,56649,56658],{"__ignoreMap":111},[115,56634,56635,56637,56639],{"class":117,"line":118},[115,56636,2764],{"class":262},[115,56638,2767],{"class":202},[115,56640,32984],{"class":132},[115,56642,56643,56645,56647],{"class":117,"line":136},[115,56644,2764],{"class":262},[115,56646,2767],{"class":202},[115,56648,13405],{"class":132},[115,56650,56651,56653,56655],{"class":117,"line":149},[115,56652,36495],{"class":262},[115,56654,2777],{"class":202},[115,56656,56657],{"class":132}," \u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log\n",[115,56659,56660,56662,56664,56666,56668],{"class":117,"line":162},[115,56661,2001],{"class":262},[115,56663,5030],{"class":132},[115,56665,2788],{"class":202},[115,56667,2791],{"class":132},[115,56669,36482],{"class":202},[16,56671,56672],{},"Important: a successful Nginx reload does not prove the Django app is healthy. The upstream process, socket target, collected static files, or the latest app release can still be broken.",[23099,56674],{},[52,56676,56678],{"id":56677},"step-6-verify-end-to-end-production-behavior","Step 6 — Verify end-to-end production behavior",[16,56680,5438],{},[63,56682,56683,56685,56688,56693,56696],{},[66,56684,15882],{},[66,56686,56687],{},"Django admin loads",[66,56689,56690,56691],{},"static files return ",[20,56692,17741],{},[66,56694,56695],{},"form submissions work",[66,56697,56698],{},"login and CSRF-protected views work over HTTPS",[16,56700,56701],{},"If secure cookies or CSRF fail behind the proxy, the usual causes are:",[63,56703,56704,56708,56712,56716],{},[66,56705,20107,56706],{},[20,56707,3203],{},[66,56709,20107,56710],{},[20,56711,2377],{},[66,56713,32032,56714],{},[20,56715,2725],{},[66,56717,31412,56718,56720],{},[20,56719,3648],{}," header handling",[16,56722,56723],{},"To inspect real behavior, use the logs:",[106,56725,56727],{"className":108,"code":56726,"language":110,"meta":111,"style":111},"tail -f \u002Fvar\u002Flog\u002Fnginx\u002Fexample.access.log\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Fexample.error.log\nsudo journalctl -u gunicorn -f\n",[20,56728,56729,56738,56746],{"__ignoreMap":111},[115,56730,56731,56733,56735],{"class":117,"line":118},[115,56732,36495],{"class":262},[115,56734,2777],{"class":202},[115,56736,56737],{"class":132}," \u002Fvar\u002Flog\u002Fnginx\u002Fexample.access.log\n",[115,56739,56740,56742,56744],{"class":117,"line":136},[115,56741,36495],{"class":262},[115,56743,2777],{"class":202},[115,56745,56657],{"class":132},[115,56747,56748,56750,56752,56754,56756],{"class":117,"line":149},[115,56749,2001],{"class":262},[115,56751,5030],{"class":132},[115,56753,2788],{"class":202},[115,56755,2791],{"class":132},[115,56757,36482],{"class":202},[16,56759,56760],{},"Rollback and recovery notes:",[63,56762,56763,56769,56772,56775],{},[66,56764,56765,56766,56768],{},"if the Nginx config is wrong, restore the previous site file, test with ",[20,56767,7611],{},", then reload",[66,56770,56771],{},"if the app release is bad, rolling back Nginx alone will not help; you may need to restore the previous Gunicorn service target, previous app code, or previous static files",[66,56773,56774],{},"if the socket path changed, fix the app server unit or Nginx upstream so they match again",[66,56776,56777,56778,56780],{},"if a new ",[20,56779,13689],{}," output is broken or missing files, restore the previous static directory or re-run the release process correctly",[16,56782,56783],{},"A practical recovery sequence is:",[1173,56785,56786,56789,56792,56797],{},[66,56787,56788],{},"confirm whether the failure is Nginx, app server, socket permission, or static-file related",[66,56790,56791],{},"restore the last known-good component",[66,56793,56794,56795],{},"test locally against the socket or ",[20,56796,20396],{},[66,56798,56799],{},"only then reload Nginx and verify through HTTPS",[11,56801,1321],{"id":1320},[16,56803,56804],{},"This setup works because Nginx handles the network-facing responsibilities while Gunicorn or Uvicorn runs the Django application process.",[16,56806,56807],{},"Nginx should serve static assets because it is more efficient than routing every CSS, JS, and image request through Django. That reduces app worker load and improves latency.",[16,56809,56810,56811,3146,56813,56815],{},"Proxy headers matter because Django uses them for security decisions. Without ",[20,56812,3648],{},[20,56814,3203],{},", Django may mis-detect the request origin or scheme, leading to bad redirects, cookie issues, or CSRF errors.",[16,56817,56818],{},"Unix sockets are usually better than localhost TCP when both services run on the same machine. They are simple and avoid exposing an extra local port. TCP is more flexible for containers, separate hosts, or debugging with common networking tools.",[16,56820,12998,56821,3146,56823,56825,56826,56829],{},[20,56822,55414],{},[20,56824,7305],{}," headers are enough for a direct Nginx-to-Django setup on one server. If Nginx is itself behind a load balancer or another proxy, client IP handling needs additional ",[20,56827,56828],{},"real_ip"," configuration in Nginx. Do not assume forwarded client IPs are trustworthy unless your proxy chain is configured explicitly.",[16,56831,56832],{},"If uploads, media storage, or traffic volume grows, split responsibilities. For example:",[63,56834,56835,56838,56841],{},[66,56836,56837],{},"keep Nginx as reverse proxy",[66,56839,56840],{},"move media to object storage",[66,56842,56843],{},"place a load balancer in front of multiple app instances",[52,56845,41766],{"id":41765},[16,56847,56848,56849,56851,56852,1153,56854,56856],{},"Once you manage more than one environment or repeat this process often, convert it into a reusable script or template. The most useful parts to automate first are config generation, backup of the previous server block, ",[20,56850,7611],{}," validation, safe reload, and basic smoke tests for ",[20,56853,44219],{},[20,56855,53499],{},", and a static asset URL.",[11,56858,1337],{"id":1336},[63,56860,56861,56867,56873,56882,56891,56897,56903,56912],{},[66,56862,56863,56866],{},[1226,56864,56865],{},"Multiple Django sites on one server:"," create one server block per domain and keep static paths separate.",[66,56868,56869,56872],{},[1226,56870,56871],{},"Dockerized Django behind host Nginx:"," TCP upstream is usually easier than a host-level Unix socket.",[66,56874,56875,56878,56879,56881],{},[1226,56876,56877],{},"Large uploads:"," increase ",[20,56880,13316],{}," intentionally. Match it to app requirements.",[66,56883,56884,56887,56888,56890],{},[1226,56885,56886],{},"Long-running requests:"," adjust ",[20,56889,53017],{},", but do not hide slow views with very large timeouts unless you understand the cause.",[66,56892,56893,56896],{},[1226,56894,56895],{},"WebSockets with ASGI:"," add upgrade headers if you use Channels or other WebSocket features. A plain WSGI setup does not need them.",[66,56898,56899,56902],{},[1226,56900,56901],{},"502\u002F504 protection:"," most 502s come from a dead app process, wrong socket path, or permission problems. Most 504s come from upstream timeouts or stalled app code.",[66,56904,56905,56908,56909,56911],{},[1226,56906,56907],{},"Local media exposure:"," serving ",[20,56910,13085],{}," directly from Nginx is fine for public files stored on the same server. If user uploads require access control, do not expose them blindly with a simple public alias.",[66,56913,56914,56917,56918,56920],{},[1226,56915,56916],{},"Secret management:"," Nginx configuration does not replace Django secret handling. Keep ",[20,56919,2713],{},", database credentials, and API tokens out of the Nginx config and manage them separately.",[11,56922,1386],{"id":1385},[16,56924,32206,56925,211],{},[1395,56926,56927],{"href":53374},"What Nginx Does in a Django Production Stack",[16,56929,56930],{},"Related implementation guides:",[63,56932,56933,56937,56942],{},[66,56934,56935],{},[1395,56936,2986],{"href":2985},[66,56938,56939],{},[1395,56940,56941],{"href":43448},"How to Serve Django Static Files in Production",[66,56943,56944],{},[1395,56945,8039],{"href":8038},[16,56947,56948,56949,211],{},"For troubleshooting, see ",[1395,56950,56951],{"href":4455},"How to Fix 502 Bad Gateway Between Nginx and Gunicorn",[11,56953,1420],{"id":1419},[52,56955,56957],{"id":56956},"should-nginx-proxy-to-gunicorn-over-a-unix-socket-or-localhost-tcp","Should Nginx proxy to Gunicorn over a Unix socket or localhost TCP?",[16,56959,56960],{},"Use a Unix socket when Nginx and Gunicorn are on the same host. Use localhost TCP when the app runs in Docker, on another host, or when you want simpler network debugging.",[52,56962,56964],{"id":56963},"how-do-i-configure-nginx-to-serve-django-static-files-in-production","How do I configure Nginx to serve Django static files in production?",[16,56966,56967,56968,56970,56971,47408],{},"Collect static files into a dedicated directory, then map ",[20,56969,11729],{}," to that directory with ",[20,56972,13001],{},[106,56974,56976],{"className":2154,"code":56975,"language":2156,"meta":111,"style":111},"location \u002Fstatic\u002F {\n    alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n}\n",[20,56977,56978,56986,56992],{"__ignoreMap":111},[115,56979,56980,56982,56984],{"class":117,"line":118},[115,56981,7128],{"class":121},[115,56983,2217],{"class":262},[115,56985,2220],{"class":125},[115,56987,56988,56990],{"class":117,"line":136},[115,56989,13026],{"class":121},[115,56991,2228],{"class":125},[115,56993,56994],{"class":117,"line":149},[115,56995,2323],{"class":125},[16,56997,56998],{},"Do not rely on Django to serve static files in normal production traffic.",[52,57000,57002],{"id":57001},"what-proxy-headers-does-django-need-behind-nginx","What proxy headers does Django need behind Nginx?",[16,57004,10908],{},[106,57006,57008],{"className":2154,"code":57007,"language":2156,"meta":111,"style":111},"proxy_set_header Host $host;\nproxy_set_header X-Real-IP $remote_addr;\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\nproxy_set_header X-Forwarded-Proto $scheme;\n",[20,57009,57010,57016,57022,57028],{"__ignoreMap":111},[115,57011,57012,57014],{"class":117,"line":118},[115,57013,8088],{"class":121},[115,57015,2288],{"class":125},[115,57017,57018,57020],{"class":117,"line":136},[115,57019,8088],{"class":121},[115,57021,3767],{"class":125},[115,57023,57024,57026],{"class":117,"line":149},[115,57025,8088],{"class":121},[115,57027,2312],{"class":125},[115,57029,57030,57032],{"class":117,"line":162},[115,57031,8088],{"class":121},[115,57033,2304],{"class":125},[16,57035,57036],{},"And in Django:",[106,57038,57039],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,57040,57041],{"__ignoreMap":111},[115,57042,57043,57045,57047,57049,57051,57053,57055],{"class":117,"line":118},[115,57044,2377],{"class":202},[115,57046,2380],{"class":121},[115,57048,2383],{"class":125},[115,57050,2386],{"class":132},[115,57052,1153],{"class":125},[115,57054,2391],{"class":132},[115,57056,2394],{"class":125},[52,57058,57060],{"id":57059},"why-does-django-show-csrf-or-insecure-request-issues-behind-nginx","Why does Django show CSRF or insecure request issues behind Nginx?",[16,57062,57063],{},"Usually because Django does not know the original request was HTTPS, or the origin is not trusted. Check:",[63,57065,57066,57071,57075,57079],{},[66,57067,57068],{},[20,57069,57070],{},"proxy_set_header X-Forwarded-Proto $scheme;",[66,57072,57073],{},[20,57074,2377],{},[66,57076,57077],{},[20,57078,2725],{},[66,57080,57081],{},[20,57082,2719],{},[52,57084,57086],{"id":57085},"how-do-i-roll-back-a-broken-nginx-config-without-taking-the-site-offline","How do I roll back a broken Nginx config without taking the site offline?",[16,57088,57089],{},"Keep a backup of the previous file, restore it, test, then reload:",[106,57091,57093],{"className":108,"code":57092,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp.bak \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp\nsudo nginx -t && sudo systemctl reload nginx\n",[20,57094,57095,57107],{"__ignoreMap":111},[115,57096,57097,57099,57101,57104],{"class":117,"line":118},[115,57098,2001],{"class":262},[115,57100,6516],{"class":132},[115,57102,57103],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp.bak",[115,57105,57106],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fmyapp\n",[115,57108,57109,57111,57113,57115,57117,57119,57121,57123],{"class":117,"line":136},[115,57110,2001],{"class":262},[115,57112,3906],{"class":132},[115,57114,3909],{"class":202},[115,57116,3912],{"class":125},[115,57118,2001],{"class":262},[115,57120,3480],{"class":132},[115,57122,3919],{"class":132},[115,57124,1996],{"class":132},[16,57126,57127],{},"If the new site config should be disabled entirely:",[106,57129,57131],{"className":108,"code":57130,"language":110,"meta":111,"style":111},"sudo rm \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fmyapp\nsudo nginx -t && sudo systemctl reload nginx\n",[20,57132,57133,57142],{"__ignoreMap":111},[115,57134,57135,57137,57139],{"class":117,"line":118},[115,57136,2001],{"class":262},[115,57138,15719],{"class":132},[115,57140,57141],{"class":132}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fmyapp\n",[115,57143,57144,57146,57148,57150,57152,57154,57156,57158],{"class":117,"line":136},[115,57145,2001],{"class":262},[115,57147,3906],{"class":132},[115,57149,3909],{"class":202},[115,57151,3912],{"class":125},[115,57153,2001],{"class":262},[115,57155,3480],{"class":132},[115,57157,3919],{"class":132},[115,57159,1996],{"class":132},[16,57161,57162],{},"If the failure is really in the Django app, socket target, or static files, roll back that part of the release too. Nginx config rollback only fixes Nginx-level mistakes.",[1485,57164,30654],{},{"title":111,"searchDepth":149,"depth":149,"links":57166},[57167,57168,57169,57177,57180,57181,57182],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":57170},[57171,57172,57173,57174,57175,57176],{"id":55436,"depth":149,"text":55437},{"id":55611,"depth":149,"text":55612},{"id":55971,"depth":149,"text":55972},{"id":56103,"depth":149,"text":56104},{"id":56553,"depth":149,"text":56554},{"id":56677,"depth":149,"text":56678},{"id":1320,"depth":136,"text":1321,"children":57178},[57179],{"id":41765,"depth":149,"text":41766},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":57183},[57184,57185,57186,57187,57188],{"id":56956,"depth":149,"text":56957},{"id":56963,"depth":149,"text":56964},{"id":57001,"depth":149,"text":57002},{"id":57059,"depth":149,"text":57060},{"id":57085,"depth":149,"text":57086},"A production Django stack should not expose Gunicorn or Uvicorn directly to the internet.",{},"\u002Fconfigure-nginx-for-django-production","16",[2985,8038,2992],{"title":55344,"description":57189},[1557,2156],"configure-nginx-for-django-production",[1557,2156],"JqXuYzJQDQPOp7rbxCtcCfXn719p130kjnoha415aqo",{"id":57200,"title":57201,"body":57202,"category":3088,"description":58612,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":58613,"navigation":309,"path":58614,"priority":58615,"related":58616,"role":1553,"section":3098,"seo":58617,"stack":58618,"stem":58619,"tags":58620,"type":1561,"__hash__":58621},"articles\u002Fsetup-systemd-for-gunicorn-django.md","How to Configure systemd for Gunicorn and Django",{"type":8,"value":57203,"toc":58576},[57204,57206,57209,57218,57220,57227,57268,57270,57274,57279,57282,57303,57306,57320,57324,57327,57358,57361,57380,57389,57393,57399,57402,57414,57417,57468,57471,57487,57490,57498,57502,57505,57516,57519,57528,57531,57540,57543,57547,57550,57564,57567,57599,57602,57627,57629,57643,57648,57652,57655,57669,57676,57790,57793,57844,57847,57875,57882,57886,57891,57904,57907,57923,57926,57941,57943,57958,57961,57983,57986,58003,58007,58022,58025,58039,58043,58046,58049,58080,58083,58112,58117,58134,58139,58143,58146,58166,58169,58184,58187,58190,58230,58234,58237,58253,58256,58282,58285,58291,58295,58299,58304,58318,58322,58331,58335,58340,58353,58357,58365,58377,58381,58384,58399,58401,58416,58422,58433,58438,58440,58443,58445,58495,58497,58517,58519,58523,58526,58530,58540,58548,58560,58564,58567,58571,58574],[11,57205,14],{"id":13},[16,57207,57208],{},"Running Gunicorn manually for a Django app is fragile. If your SSH session closes, the process can stop. If the server reboots, the app does not come back unless you start it again. You also lose a clean way to restart the service, inspect logs, and control how failures are handled.",[16,57210,57211,57212,57214,57215,57217],{},"A production-safe pattern is to run Gunicorn under ",[20,57213,1277],{},". This gives you boot-time startup, supervised restarts, standard service management commands, and centralized logs. This page shows how to configure ",[20,57216,1277],{}," for Gunicorn and Django on a single Linux server, with a reverse proxy such as Nginx in front.",[11,57219,30],{"id":29},[16,57221,57222,57223,57226],{},"For a reliable ",[20,57224,57225],{},"systemd gunicorn django"," setup:",[1173,57228,57229,57232,57235,57240,57243,57252,57258,57265],{},[66,57230,57231],{},"Run Gunicorn as a dedicated non-root user.",[66,57233,57234],{},"Confirm Gunicorn starts manually from the correct virtualenv.",[66,57236,29658,57237,57239],{},[20,57238,1277],{}," service unit pointing at the project directory, environment file, and Gunicorn binary.",[66,57241,57242],{},"Bind Gunicorn to a Unix socket or localhost TCP port.",[66,57244,57245,57246,57248,57249,57251],{},"If you use a Unix socket under ",[20,57247,31397],{},", configure ",[20,57250,13821],{}," so it is recreated on boot.",[66,57253,57254,57255,57257],{},"Reload ",[20,57256,1277],{},", start the service, and enable it on boot.",[66,57259,57260,57261,3146,57263,211],{},"Verify with ",[20,57262,23834],{},[20,57264,2785],{},[66,57266,57267],{},"Put Nginx or Caddy in front for proxying, static files, and TLS.",[11,57269,43],{"id":42},[11,57271,57273],{"id":57272},"when-to-use-systemd-for-gunicorn-and-django","When to use systemd for Gunicorn and Django",[16,57275,57276,57278],{},[20,57277,1277],{}," is a good fit when your Django app runs directly on a Linux host or VM and you want standard process supervision.",[16,57280,57281],{},"What it solves:",[63,57283,57284,57287,57290,57295,57300],{},[66,57285,57286],{},"starts Gunicorn on boot",[66,57288,57289],{},"restarts it after failures",[66,57291,57292,57293],{},"provides standard service commands with ",[20,57294,1981],{},[66,57296,57297,57298],{},"stores logs in ",[20,57299,35702],{},[66,57301,57302],{},"makes deployments repeatable",[16,57304,57305],{},"What it does not replace:",[63,57307,57308,57310,57312,57315,57317],{},[66,57309,13713],{},[66,57311,1785],{},[66,57313,57314],{},"metrics and alerting",[66,57316,2447],{},[66,57318,57319],{},"rollback planning",[11,57321,57323],{"id":57322},"_1-confirm-your-paths-and-runtime-user","1. Confirm your paths and runtime user",[16,57325,57326],{},"Use consistent paths before writing the service file. Example layout:",[63,57328,57329,57334,57340,57346,57352],{},[66,57330,57331,57332],{},"app user: ",[20,57333,1557],{},[66,57335,57336,57337],{},"project root: ",[20,57338,57339],{},"\u002Fsrv\u002Fmyproject",[66,57341,57342,57343],{},"current release: ",[20,57344,57345],{},"\u002Fsrv\u002Fmyproject\u002Fcurrent",[66,57347,57348,57349],{},"virtualenv: ",[20,57350,57351],{},"\u002Fsrv\u002Fmyproject\u002Fvenv",[66,57353,57354,57355],{},"socket: ",[20,57356,57357],{},"\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock",[16,57359,57360],{},"Check ownership:",[106,57362,57364],{"className":108,"code":57363,"language":110,"meta":111,"style":111},"sudo chown -R django:django \u002Fsrv\u002Fmyproject\n",[20,57365,57366],{"__ignoreMap":111},[115,57367,57368,57370,57372,57374,57377],{"class":117,"line":118},[115,57369,2001],{"class":262},[115,57371,6733],{"class":132},[115,57373,6736],{"class":202},[115,57375,57376],{"class":132}," django:django",[115,57378,57379],{"class":132}," \u002Fsrv\u002Fmyproject\n",[16,57381,57382,57383,57385,57386,57388],{},"If Nginx will connect to the Unix socket, a common pattern is to run Gunicorn as user ",[20,57384,1557],{}," and group ",[20,57387,12660],{},", so the proxy can access the socket.",[11,57390,57392],{"id":57391},"_2-verify-gunicorn-works-manually-first","2. Verify Gunicorn works manually first",[16,57394,57395,57396,57398],{},"Before involving ",[20,57397,1277],{},", confirm that Gunicorn can import your Django app from the correct virtualenv.",[16,57400,57401],{},"Check the binary:",[106,57403,57405],{"className":108,"code":57404,"language":110,"meta":111,"style":111},"\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn --version\n",[20,57406,57407],{"__ignoreMap":111},[115,57408,57409,57412],{"class":117,"line":118},[115,57410,57411],{"class":262},"\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn",[115,57413,6614],{"class":202},[16,57415,57416],{},"Test a manual start:",[106,57418,57420],{"className":108,"code":57419,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Frun\u002Fgunicorn-myproject\nsudo chown django:www-data \u002Frun\u002Fgunicorn-myproject\ncd \u002Fsrv\u002Fmyproject\u002Fcurrent\n\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock myproject.wsgi:application\n",[20,57421,57422,57433,57444,57451],{"__ignoreMap":111},[115,57423,57424,57426,57428,57430],{"class":117,"line":118},[115,57425,2001],{"class":262},[115,57427,6721],{"class":132},[115,57429,1001],{"class":202},[115,57431,57432],{"class":132}," \u002Frun\u002Fgunicorn-myproject\n",[115,57434,57435,57437,57439,57442],{"class":117,"line":136},[115,57436,2001],{"class":262},[115,57438,6733],{"class":132},[115,57440,57441],{"class":132}," django:www-data",[115,57443,57432],{"class":132},[115,57445,57446,57448],{"class":117,"line":149},[115,57447,5303],{"class":202},[115,57449,57450],{"class":132}," \u002Fsrv\u002Fmyproject\u002Fcurrent\n",[115,57452,57453,57455,57458,57460,57462,57465],{"class":117,"line":162},[115,57454,57411],{"class":262},[115,57456,57457],{"class":202}," --workers",[115,57459,36545],{"class":202},[115,57461,23605],{"class":202},[115,57463,57464],{"class":132}," unix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock",[115,57466,57467],{"class":132}," myproject.wsgi:application\n",[16,57469,57470],{},"If this fails, fix that first. Common failures here are:",[63,57472,57473,57478,57481,57484],{},[66,57474,57475,57476],{},"wrong module path like ",[20,57477,29785],{},[66,57479,57480],{},"missing environment variables",[66,57482,57483],{},"missing database settings",[66,57485,57486],{},"bad virtualenv path",[16,57488,57489],{},"Stop the manual process after testing.",[16,57491,57492,57494,57495,57497],{},[1226,57493,3515],{}," Gunicorn should start without immediate import errors. If it crashes, do not continue to the ",[20,57496,1277],{}," unit yet.",[11,57499,57501],{"id":57500},"_3-decide-between-a-unix-socket-and-a-tcp-port","3. Decide between a Unix socket and a TCP port",[16,57503,57504],{},"For a single host with Nginx on the same machine, prefer a Unix socket:",[63,57506,57507,57510,57513],{},[66,57508,57509],{},"slightly simpler local proxying",[66,57511,57512],{},"no exposed listening port",[66,57514,57515],{},"common production pattern",[16,57517,57518],{},"Example socket bind:",[106,57520,57522],{"className":108,"code":57521,"language":110,"meta":111,"style":111},"unix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock\n",[20,57523,57524],{"__ignoreMap":111},[115,57525,57526],{"class":117,"line":118},[115,57527,57521],{"class":262},[16,57529,57530],{},"Use a TCP port if needed for a different layout, such as localhost proxying or some container edge cases:",[106,57532,57534],{"className":108,"code":57533,"language":110,"meta":111,"style":111},"127.0.0.1:8000\n",[20,57535,57536],{"__ignoreMap":111},[115,57537,57538],{"class":117,"line":118},[115,57539,57533],{"class":262},[16,57541,57542],{},"If you use TCP, bind to localhost only unless you intentionally want external access.",[11,57544,57546],{"id":57545},"_4-create-an-environment-file","4. Create an environment file",[16,57548,57549],{},"Keep most environment variables out of the service unit. Create a root-owned file:",[106,57551,57553],{"className":108,"code":57552,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fmyproject.env\n",[20,57554,57555],{"__ignoreMap":111},[115,57556,57557,57559,57561],{"class":117,"line":118},[115,57558,2001],{"class":262},[115,57560,12408],{"class":132},[115,57562,57563],{"class":132}," \u002Fetc\u002Fmyproject.env\n",[16,57565,57566],{},"Example contents:",[106,57568,57570],{"className":21525,"code":57569,"language":21527,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=myproject.settings.production\nDJANGO_DEBUG=False\nSECRET_KEY=replace-me\nDATABASE_URL=replace-me\nALLOWED_HOSTS=example.com,www.example.com\nCSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n",[20,57571,57572,57577,57581,57586,57591,57595],{"__ignoreMap":111},[115,57573,57574],{"class":117,"line":118},[115,57575,57576],{},"DJANGO_SETTINGS_MODULE=myproject.settings.production\n",[115,57578,57579],{"class":117,"line":136},[115,57580,15015],{},[115,57582,57583],{"class":117,"line":149},[115,57584,57585],{},"SECRET_KEY=replace-me\n",[115,57587,57588],{"class":117,"line":162},[115,57589,57590],{},"DATABASE_URL=replace-me\n",[115,57592,57593],{"class":117,"line":175},[115,57594,2358],{},[115,57596,57597],{"class":117,"line":350},[115,57598,2363],{},[16,57600,57601],{},"Set restrictive permissions:",[106,57603,57605],{"className":108,"code":57604,"language":110,"meta":111,"style":111},"sudo chown root:root \u002Fetc\u002Fmyproject.env\nsudo chmod 600 \u002Fetc\u002Fmyproject.env\n",[20,57606,57607,57617],{"__ignoreMap":111},[115,57608,57609,57611,57613,57615],{"class":117,"line":118},[115,57610,2001],{"class":262},[115,57612,6733],{"class":132},[115,57614,23394],{"class":132},[115,57616,57563],{"class":132},[115,57618,57619,57621,57623,57625],{"class":117,"line":136},[115,57620,2001],{"class":262},[115,57622,12480],{"class":132},[115,57624,266],{"class":202},[115,57626,57563],{"class":132},[16,57628,8508],{},[63,57630,57631,57640],{},[66,57632,57633,57636,57637,57639],{},[20,57634,57635],{},"DJANGO_DEBUG=False"," only works if your project reads that environment variable and maps it to Django’s ",[20,57638,7350],{}," setting.",[66,57641,57642],{},"Do not commit real secrets into source control. Use this file only on the server, with real values.",[16,57644,57645,57647],{},[1226,57646,3515],{}," confirm the file is not world-readable.",[11,57649,57651],{"id":57650},"_5-create-the-gunicorn-systemd-service-file","5. Create the Gunicorn systemd service file",[16,57653,57654],{},"Create the service unit:",[106,57656,57658],{"className":108,"code":57657,"language":110,"meta":111,"style":111},"sudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-myproject.service\n",[20,57659,57660],{"__ignoreMap":111},[115,57661,57662,57664,57666],{"class":117,"line":118},[115,57663,2001],{"class":262},[115,57665,12408],{"class":132},[115,57667,57668],{"class":132}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-myproject.service\n",[16,57670,57671,57672,57675],{},"Use this example ",[20,57673,57674],{},"django gunicorn systemd service"," file:",[106,57677,57679],{"className":2026,"code":57678,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for Django project myproject\nAfter=network.target\n\n[Service]\nUser=django\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyproject\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyproject.env\nRuntimeDirectory=gunicorn-myproject\nUMask=0007\nExecStart=\u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn \\\n    --workers 3 \\\n    --bind unix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock \\\n    myproject.wsgi:application\nRestart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n",[20,57680,57681,57685,57692,57698,57702,57706,57712,57718,57724,57731,57738,57745,57751,57755,57760,57764,57770,57776,57780,57784],{"__ignoreMap":111},[115,57682,57683],{"class":117,"line":118},[115,57684,2035],{"class":262},[115,57686,57687,57689],{"class":117,"line":136},[115,57688,2040],{"class":121},[115,57690,57691],{"class":125},"=Gunicorn for Django project myproject\n",[115,57693,57694,57696],{"class":117,"line":149},[115,57695,2048],{"class":121},[115,57697,2051],{"class":125},[115,57699,57700],{"class":117,"line":162},[115,57701,310],{"emptyLinePlaceholder":309},[115,57703,57704],{"class":117,"line":175},[115,57705,2060],{"class":262},[115,57707,57708,57710],{"class":117,"line":350},[115,57709,2065],{"class":121},[115,57711,2068],{"class":125},[115,57713,57714,57716],{"class":117,"line":365},[115,57715,2073],{"class":121},[115,57717,2076],{"class":125},[115,57719,57720,57722],{"class":117,"line":380},[115,57721,2081],{"class":121},[115,57723,52359],{"class":125},[115,57725,57726,57728],{"class":117,"line":487},[115,57727,2089],{"class":121},[115,57729,57730],{"class":125},"=\u002Fetc\u002Fmyproject.env\n",[115,57732,57733,57735],{"class":117,"line":2095},[115,57734,2098],{"class":121},[115,57736,57737],{"class":125},"=gunicorn-myproject\n",[115,57739,57740,57742],{"class":117,"line":2104},[115,57741,12588],{"class":121},[115,57743,57744],{"class":125},"=0007\n",[115,57746,57747,57749],{"class":117,"line":2113},[115,57748,2107],{"class":121},[115,57750,52366],{"class":125},[115,57752,57753],{"class":117,"line":2122},[115,57754,15417],{"class":125},[115,57756,57757],{"class":117,"line":2131},[115,57758,57759],{"class":125},"    --bind unix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock \\\n",[115,57761,57762],{"class":117,"line":2136},[115,57763,29753],{"class":125},[115,57765,57766,57768],{"class":117,"line":2142},[115,57767,2116],{"class":121},[115,57769,2119],{"class":125},[115,57771,57772,57774],{"class":117,"line":2273},[115,57773,2125],{"class":121},[115,57775,2128],{"class":125},[115,57777,57778],{"class":117,"line":2282},[115,57779,310],{"emptyLinePlaceholder":309},[115,57781,57782],{"class":117,"line":2291},[115,57783,2139],{"class":262},[115,57785,57786,57788],{"class":117,"line":2299},[115,57787,2145],{"class":121},[115,57789,2148],{"class":125},[16,57791,57792],{},"Important fields:",[63,57794,57795,57800,57805,57810,57815,57827,57833,57838],{},[66,57796,57797,57799],{},[20,57798,2065],{},": do not run Gunicorn as root",[66,57801,57802,57804],{},[20,57803,2073],{},": set this so Nginx can access the socket if needed",[66,57806,57807,57809],{},[20,57808,2081],{},": should match the Django project location",[66,57811,57812,57814],{},[20,57813,2089],{},": loads settings outside the unit file",[66,57816,57817,57819,57820,57823,57824,57826],{},[20,57818,2098],{},": creates ",[20,57821,57822],{},"\u002Frun\u002Fgunicorn-myproject"," on boot, which is important because ",[20,57825,31397],{}," is cleared on reboot",[66,57828,57829,57832],{},[20,57830,57831],{},"UMask=0007",": helps keep the socket group-accessible while not making it world-accessible",[66,57834,57835,57837],{},[20,57836,2107],{},": full path to Gunicorn in the virtualenv",[66,57839,57840,57843],{},[20,57841,57842],{},"Restart=on-failure",": restarts after crashes, not after clean stops",[16,57845,57846],{},"Optional hardening directives, compatibility-dependent:",[106,57848,57850],{"className":2026,"code":57849,"language":2028,"meta":111,"style":111},"PrivateTmp=true\nNoNewPrivileges=true\nProtectSystem=full\n",[20,57851,57852,57860,57867],{"__ignoreMap":111},[115,57853,57854,57857],{"class":117,"line":118},[115,57855,57856],{"class":121},"PrivateTmp",[115,57858,57859],{"class":125},"=true\n",[115,57861,57862,57865],{"class":117,"line":136},[115,57863,57864],{"class":121},"NoNewPrivileges",[115,57866,57859],{"class":125},[115,57868,57869,57872],{"class":117,"line":149},[115,57870,57871],{"class":121},"ProtectSystem",[115,57873,57874],{"class":125},"=full\n",[16,57876,57877,57878,57881],{},"Add these only after testing. If you enable filesystem hardening, also define writable locations explicitly where needed, for example with ",[20,57879,57880],{},"ReadWritePaths="," for media, temp, or release-managed directories.",[11,57883,57885],{"id":57884},"_6-load-start-and-enable-the-service","6. Load, start, and enable the service",[16,57887,57254,57888,57890],{},[20,57889,1277],{}," after creating the unit:",[106,57892,57894],{"className":108,"code":57893,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\n",[20,57895,57896],{"__ignoreMap":111},[115,57897,57898,57900,57902],{"class":117,"line":118},[115,57899,2001],{"class":262},[115,57901,3480],{"class":132},[115,57903,4984],{"class":132},[16,57905,57906],{},"Start the service:",[106,57908,57910],{"className":108,"code":57909,"language":110,"meta":111,"style":111},"sudo systemctl start gunicorn-myproject\n",[20,57911,57912],{"__ignoreMap":111},[115,57913,57914,57916,57918,57920],{"class":117,"line":118},[115,57915,2001],{"class":262},[115,57917,3480],{"class":132},[115,57919,15489],{"class":132},[115,57921,57922],{"class":132}," gunicorn-myproject\n",[16,57924,57925],{},"Enable it on boot:",[106,57927,57929],{"className":108,"code":57928,"language":110,"meta":111,"style":111},"sudo systemctl enable gunicorn-myproject\n",[20,57930,57931],{"__ignoreMap":111},[115,57932,57933,57935,57937,57939],{"class":117,"line":118},[115,57934,2001],{"class":262},[115,57936,3480],{"class":132},[115,57938,8567],{"class":132},[115,57940,57922],{"class":132},[16,57942,26640],{},[106,57944,57946],{"className":108,"code":57945,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn-myproject\n",[20,57947,57948],{"__ignoreMap":111},[115,57949,57950,57952,57954,57956],{"class":117,"line":118},[115,57951,2001],{"class":262},[115,57953,3480],{"class":132},[115,57955,1984],{"class":132},[115,57957,57922],{"class":132},[16,57959,57960],{},"View recent logs:",[106,57962,57964],{"className":108,"code":57963,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn-myproject -n 50 --no-pager\n",[20,57965,57966],{"__ignoreMap":111},[115,57967,57968,57970,57972,57974,57977,57979,57981],{"class":117,"line":118},[115,57969,2001],{"class":262},[115,57971,5030],{"class":132},[115,57973,2788],{"class":202},[115,57975,57976],{"class":132}," gunicorn-myproject",[115,57978,2794],{"class":202},[115,57980,15523],{"class":202},[115,57982,2800],{"class":202},[16,57984,57985],{},"Follow logs live:",[106,57987,57989],{"className":108,"code":57988,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn-myproject -f\n",[20,57990,57991],{"__ignoreMap":111},[115,57992,57993,57995,57997,57999,58001],{"class":117,"line":118},[115,57994,2001],{"class":262},[115,57996,5030],{"class":132},[115,57998,2788],{"class":202},[115,58000,57976],{"class":132},[115,58002,36482],{"class":202},[16,58004,58005],{},[1226,58006,17389],{},[63,58008,58009,58016,58019],{},[66,58010,58011,58013,58014],{},[20,58012,23834],{}," shows ",[20,58015,30925],{},[66,58017,58018],{},"logs do not show repeated restart loops",[66,58020,58021],{},"the socket or localhost port exists as expected",[16,58023,58024],{},"For a Unix socket, verify:",[106,58026,58028],{"className":108,"code":58027,"language":110,"meta":111,"style":111},"ls -l \u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock\n",[20,58029,58030],{"__ignoreMap":111},[115,58031,58032,58034,58036],{"class":117,"line":118},[115,58033,532],{"class":262},[115,58035,14881],{"class":202},[115,58037,58038],{"class":132}," \u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock\n",[11,58040,58042],{"id":58041},"_7-connect-nginx-to-gunicorn","7. Connect Nginx to Gunicorn",[16,58044,58045],{},"If you use a Unix socket, make sure the Nginx config points to the same path.",[16,58047,58048],{},"Example snippet:",[106,58050,58052],{"className":2154,"code":58051,"language":2156,"meta":111,"style":111},"location \u002F {\n    include proxy_params;\n    proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock;\n}\n",[20,58053,58054,58062,58069,58076],{"__ignoreMap":111},[115,58055,58056,58058,58060],{"class":117,"line":118},[115,58057,7128],{"class":121},[115,58059,2268],{"class":262},[115,58061,2220],{"class":125},[115,58063,58064,58067],{"class":117,"line":136},[115,58065,58066],{"class":121},"    include ",[115,58068,15657],{"class":125},[115,58070,58071,58073],{"class":117,"line":149},[115,58072,7137],{"class":121},[115,58074,58075],{"class":125},"http:\u002F\u002Funix:\u002Frun\u002Fgunicorn-myproject\u002Fgunicorn.sock;\n",[115,58077,58078],{"class":117,"line":162},[115,58079,2323],{"class":125},[16,58081,58082],{},"If you use TCP instead, proxy to localhost:",[106,58084,58086],{"className":2154,"code":58085,"language":2156,"meta":111,"style":111},"location \u002F {\n    include proxy_params;\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n}\n",[20,58087,58088,58096,58102,58108],{"__ignoreMap":111},[115,58089,58090,58092,58094],{"class":117,"line":118},[115,58091,7128],{"class":121},[115,58093,2268],{"class":262},[115,58095,2220],{"class":125},[115,58097,58098,58100],{"class":117,"line":136},[115,58099,58066],{"class":121},[115,58101,15657],{"class":125},[115,58103,58104,58106],{"class":117,"line":149},[115,58105,7137],{"class":121},[115,58107,3748],{"class":125},[115,58109,58110],{"class":117,"line":162},[115,58111,2323],{"class":125},[16,58113,13158,58114,58116],{},[20,58115,13161],{},", check:",[63,58118,58119,58122,58125,58128,58131],{},[66,58120,58121],{},"Gunicorn is running",[66,58123,58124],{},"socket path matches exactly",[66,58126,58127],{},"socket permissions allow Nginx access",[66,58129,58130],{},"Gunicorn is bound to the expected TCP port or socket",[66,58132,58133],{},"the Django app did not crash on startup",[16,58135,58136,58138],{},[1226,58137,3515],{}," reload Nginx and request the app through the public web server, not just directly through Gunicorn.",[11,58140,58142],{"id":58141},"_8-handle-deployments-safely","8. Handle deployments safely",[16,58144,58145],{},"A normal release flow should be:",[1173,58147,58148,58151,58153,58155,58158,58160,58163],{},[66,58149,58150],{},"upload or switch code",[66,58152,33491],{},[66,58154,1186],{},[66,58156,58157],{},"collect static files",[66,58159,22673],{},[66,58161,58162],{},"verify health",[66,58164,58165],{},"keep the previous release available until checks pass",[16,58167,58168],{},"Restart Gunicorn after a deploy:",[106,58170,58172],{"className":108,"code":58171,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn-myproject\n",[20,58173,58174],{"__ignoreMap":111},[115,58175,58176,58178,58180,58182],{"class":117,"line":118},[115,58177,2001],{"class":262},[115,58179,3480],{"class":132},[115,58181,3483],{"class":132},[115,58183,57922],{"class":132},[16,58185,58186],{},"A full restart is usually safer than trying to optimize for partial reload behavior during early deployments.",[16,58188,58189],{},"Example verification after deploy:",[106,58191,58193],{"className":108,"code":58192,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn-myproject\nsudo journalctl -u gunicorn-myproject -n 50 --no-pager\ncurl -I http:\u002F\u002F127.0.0.1\u002F\n",[20,58194,58195,58205,58221],{"__ignoreMap":111},[115,58196,58197,58199,58201,58203],{"class":117,"line":118},[115,58198,2001],{"class":262},[115,58200,3480],{"class":132},[115,58202,1984],{"class":132},[115,58204,57922],{"class":132},[115,58206,58207,58209,58211,58213,58215,58217,58219],{"class":117,"line":136},[115,58208,2001],{"class":262},[115,58210,5030],{"class":132},[115,58212,2788],{"class":202},[115,58214,57976],{"class":132},[115,58216,2794],{"class":202},[115,58218,15523],{"class":202},[115,58220,2800],{"class":202},[115,58222,58223,58225,58227],{"class":117,"line":149},[115,58224,2764],{"class":262},[115,58226,2767],{"class":202},[115,58228,58229],{"class":132}," http:\u002F\u002F127.0.0.1\u002F\n",[52,58231,58233],{"id":58232},"rollback-plan","Rollback plan",[16,58235,58236],{},"If a new release fails:",[1173,58238,58239,58245,58247,58250],{},[66,58240,58241,58242,58244],{},"point your ",[20,58243,13654],{}," symlink back to the previous release",[66,58246,22673],{},[66,58248,58249],{},"verify status and logs",[66,58251,58252],{},"test the site through Nginx",[16,58254,58255],{},"Example if you use release symlinks:",[106,58257,58259],{"className":108,"code":58258,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyproject\u002Freleases\u002Fprevious \u002Fsrv\u002Fmyproject\u002Fcurrent\nsudo systemctl restart gunicorn-myproject\n",[20,58260,58261,58272],{"__ignoreMap":111},[115,58262,58263,58265,58267,58270],{"class":117,"line":118},[115,58264,14854],{"class":262},[115,58266,14857],{"class":202},[115,58268,58269],{"class":132}," \u002Fsrv\u002Fmyproject\u002Freleases\u002Fprevious",[115,58271,57450],{"class":132},[115,58273,58274,58276,58278,58280],{"class":117,"line":136},[115,58275,2001],{"class":262},[115,58277,3480],{"class":132},[115,58279,3483],{"class":132},[115,58281,57922],{"class":132},[16,58283,58284],{},"Do not delete the previous release until the new one passes health checks.",[16,58286,58287,58288,58290],{},"If the failed release included backward-incompatible database migrations, switching the ",[20,58289,13654],{}," symlink back may not fully restore the app. Treat schema rollback as a separate procedure and test it before relying on it in production.",[11,58292,58294],{"id":58293},"_9-common-systemd-and-gunicorn-issues","9. Common systemd and Gunicorn issues",[52,58296,58298],{"id":58297},"virtualenv-path-is-wrong","Virtualenv path is wrong",[16,58300,6168,58301,58303],{},[20,58302,2107],{}," points to a missing Gunicorn binary, the service will fail immediately. Check:",[106,58305,58307],{"className":108,"code":58306,"language":110,"meta":111,"style":111},"ls -l \u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn\n",[20,58308,58309],{"__ignoreMap":111},[115,58310,58311,58313,58315],{"class":117,"line":118},[115,58312,532],{"class":262},[115,58314,14881],{"class":202},[115,58316,58317],{"class":132}," \u002Fsrv\u002Fmyproject\u002Fvenv\u002Fbin\u002Fgunicorn\n",[52,58319,58321],{"id":58320},"environment-variables-are-missing","Environment variables are missing",[16,58323,58324,58325,58327,58328,58330],{},"If the app works in a shell but not in ",[20,58326,1277],{},", your shell may be loading variables that the service does not. Put required values in ",[20,58329,2089],{}," and restart.",[52,58332,58334],{"id":58333},"workingdirectory-or-module-path-is-wrong","WorkingDirectory or module path is wrong",[16,58336,6168,58337,58339],{},[20,58338,29785],{}," cannot be imported, confirm:",[63,58341,58342,58347,58350],{},[66,58343,58344,58346],{},[20,58345,2081],{}," points at the correct project",[66,58348,58349],{},"the Python package name is correct",[66,58351,58352],{},"the code exists in the current release path",[52,58354,58356],{"id":58355},"socket-permission-denied","Socket permission denied",[16,58358,58359,58360,1153,58362,58364],{},"If Nginx cannot connect to the socket, check the service ",[20,58361,2073],{},[20,58363,12588],{},", and the socket ownership:",[106,58366,58367],{"className":108,"code":58027,"language":110,"meta":111,"style":111},[20,58368,58369],{"__ignoreMap":111},[115,58370,58371,58373,58375],{"class":117,"line":118},[115,58372,532],{"class":262},[115,58374,14881],{"class":202},[115,58376,58038],{"class":132},[52,58378,58380],{"id":58379},"gunicorn-runs-but-nginx-returns-502","Gunicorn runs but Nginx returns 502",[16,58382,58383],{},"This usually means:",[63,58385,58386,58390,58393,58396],{},[66,58387,31412,58388],{},[20,58389,54909],{},[66,58391,58392],{},"wrong socket path",[66,58394,58395],{},"permission issue on the socket",[66,58397,58398],{},"Gunicorn crashed after initial startup",[11,58400,1321],{"id":1320},[16,58402,58403,58405,58406,1153,58409,47917,58412,58415],{},[20,58404,1277],{}," is preferred over ",[20,58407,58408],{},"nohup",[20,58410,58411],{},"screen",[20,58413,58414],{},"cron @reboot"," because it provides real service supervision. You get controlled restarts, boot integration, and logs in one place. That makes failures easier to detect and deployments easier to standardize.",[16,58417,58418,58419,58421],{},"Restart policies improve reliability because a transient application crash does not require manual intervention. ",[20,58420,57842],{}," is a reasonable default for Gunicorn in production.",[16,58423,13818,58424,58426,58427,58429,58430,58432],{},[20,58425,13821],{}," also makes Unix socket setups reliable across reboots. Without it, a socket path under ",[20,58428,31397],{}," can disappear after restart because ",[20,58431,31397],{}," is temporary.",[16,58434,58435,58437],{},[20,58436,35702],{}," helps because you can inspect recent logs with a single command instead of searching ad hoc log files. That is especially useful when a deployment fails and the service exits before the reverse proxy can serve traffic.",[52,58439,41766],{"id":41765},[16,58441,58442],{},"If you deploy multiple Django apps or repeat this setup across environments, this manual process becomes a good candidate for a reusable template or script. The service file, environment file layout, and restart or health-check commands are usually consistent. A small deploy script can also restart Gunicorn, verify status, and switch back to a previous release path when application-level checks fail.",[11,58444,1337],{"id":1336},[63,58446,58447,58454,58463,58472,58477,58483,58489],{},[66,58448,58449,2957,58451,58453],{},[1226,58450,41808],{},[20,58452,1277],{}," only manages Gunicorn. You still need a plan for static and media handling, usually through Nginx or object storage.",[66,58455,58456,58458,58459,58462],{},[1226,58457,2932],{}," if TLS terminates at Nginx or Caddy, configure Django to trust the proxy’s HTTPS header correctly, typically with ",[20,58460,58461],{},"SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')",", and only when that header is set by your reverse proxy.",[66,58464,58465,58468,58469,58471],{},[1226,58466,58467],{},"CSRF over HTTPS:"," if your deployment uses HTTPS and a reverse proxy, make sure your Django settings include the correct ",[20,58470,2725],{}," values for your production domains.",[66,58473,58474,58476],{},[1226,58475,2944],{}," if requests are long-running, review Gunicorn worker settings carefully instead of relying on defaults.",[66,58478,58479,58482],{},[1226,58480,58481],{},"Multiple apps on one server:"," use separate service names, sockets, and preferably separate users where practical.",[66,58484,58485,58488],{},[1226,58486,58487],{},"systemd socket units:"," socket activation exists, but it is usually unnecessary for standard Django deployments and adds complexity.",[66,58490,58491,58494],{},[1226,58492,58493],{},"Reboot test:"," after enabling the service, reboot once during a maintenance window and confirm the app comes back automatically.",[11,58496,1386],{"id":1385},[16,58498,58499,58500,211,58502,58504,58505,211,58507,58509,58510,211,58512,58514,58515,211],{},"To place this setup in context, read ",[1395,58501,3000],{"href":2999},[20160,58503],{},"\nFor the full web stack, continue with ",[1395,58506,2986],{"href":2985},[20160,58508],{},"\nIf you are deploying an ASGI app instead of WSGI, see ",[1395,58511,8039],{"href":8038},[20160,58513],{},"\nIf you want an alternative reverse proxy, read ",[1395,58516,8046],{"href":8045},[11,58518,1420],{"id":1419},[52,58520,58522],{"id":58521},"should-i-use-a-unix-socket-or-a-tcp-port-for-gunicorn-with-systemd","Should I use a Unix socket or a TCP port for Gunicorn with systemd?",[16,58524,58525],{},"Use a Unix socket when Nginx and Gunicorn run on the same host. Use a localhost TCP port when that fits your topology better. Do not bind Gunicorn to a public interface unless you have a specific reason.",[52,58527,58529],{"id":58528},"where-should-i-store-django-environment-variables-when-using-systemd","Where should I store Django environment variables when using systemd?",[16,58531,58532,58533,58535,58536,58539],{},"Use an ",[20,58534,2089],{}," such as ",[20,58537,58538],{},"\u002Fetc\u002Fmyproject.env"," with restrictive permissions. Avoid putting secrets directly into the service unit when possible.",[52,58541,58543,58544,58547],{"id":58542},"why-does-systemctl-start-fail-even-though-gunicorn-works-manually","Why does ",[20,58545,58546],{},"systemctl start"," fail even though Gunicorn works manually?",[16,58549,58550,58551,58553,58554,1153,58556,20346,58558,211],{},"Usually because ",[20,58552,1277],{}," is missing the same environment, working directory, or binary path you used in the shell. Compare the manual command with ",[20,58555,2107],{},[20,58557,2081],{},[20,58559,2089],{},[52,58561,58563],{"id":58562},"do-i-need-to-restart-gunicorn-after-every-django-deployment","Do I need to restart Gunicorn after every Django deployment?",[16,58565,58566],{},"Usually yes. After code changes, migrations, or dependency updates, restart Gunicorn so workers load the new release. Run migrations and collect static files before the restart.",[52,58568,58570],{"id":58569},"can-i-run-multiple-django-apps-with-separate-gunicorn-systemd-services-on-one-server","Can I run multiple Django apps with separate Gunicorn systemd services on one server?",[16,58572,58573],{},"Yes. Give each app its own service name, project path, socket or port, and environment file. Separate users are also a good idea when practical.",[1485,58575,30654],{},{"title":111,"searchDepth":149,"depth":149,"links":58577},[58578,58579,58580,58581,58582,58583,58584,58585,58586,58587,58588,58589,58592,58599,58602,58603,58604],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":57272,"depth":136,"text":57273},{"id":57322,"depth":136,"text":57323},{"id":57391,"depth":136,"text":57392},{"id":57500,"depth":136,"text":57501},{"id":57545,"depth":136,"text":57546},{"id":57650,"depth":136,"text":57651},{"id":57884,"depth":136,"text":57885},{"id":58041,"depth":136,"text":58042},{"id":58141,"depth":136,"text":58142,"children":58590},[58591],{"id":58232,"depth":149,"text":58233},{"id":58293,"depth":136,"text":58294,"children":58593},[58594,58595,58596,58597,58598],{"id":58297,"depth":149,"text":58298},{"id":58320,"depth":149,"text":58321},{"id":58333,"depth":149,"text":58334},{"id":58355,"depth":149,"text":58356},{"id":58379,"depth":149,"text":58380},{"id":1320,"depth":136,"text":1321,"children":58600},[58601],{"id":41765,"depth":149,"text":41766},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":58605},[58606,58607,58608,58610,58611],{"id":58521,"depth":149,"text":58522},{"id":58528,"depth":149,"text":58529},{"id":58542,"depth":149,"text":58609},"Why does systemctl start fail even though Gunicorn works manually?",{"id":58562,"depth":149,"text":58563},{"id":58569,"depth":149,"text":58570},"Running Gunicorn manually for a Django app is fragile. If your SSH session closes, the process can stop.",{},"\u002Fsetup-systemd-for-gunicorn-django","15",[2985,8045,14027],{"title":57201,"description":58612},[1557,14954,1277],"setup-systemd-for-gunicorn-django",[1557,14954,1277],"cLVeLUlSKZixkZ9TkktoFuf1EU8Kr3n7pQ6L8-XAgVg",{"id":58623,"title":58624,"body":58625,"category":3088,"description":60116,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":1546,"meta":60117,"navigation":309,"path":60118,"priority":60119,"related":60120,"role":1553,"section":3098,"seo":60122,"stack":60123,"stem":60124,"tags":60125,"type":1561,"__hash__":60126},"articles\u002Fconnect-django-to-postgresql-production.md","How to Connect Django to PostgreSQL in Production",{"type":8,"value":58626,"toc":60045},[58627,58629,58635,58642,58645,58647,58650,58682,58684,58686,58690,58694,58697,58751,58758,58793,58796,58817,58820,58824,58827,58831,58834,58857,58860,58864,58867,58882,58885,58892,58894,58898,58907,58916,58919,58932,58934,58948,58952,58967,58970,58974,58987,58990,59003,59007,59010,59012,59016,59023,59026,59248,59252,59258,59300,59306,59313,59316,59332,59335,59339,59342,59346,59348,59362,59364,59368,59372,59378,59383,59397,59400,59426,59429,59433,59436,59454,59458,59461,59463,59467,59471,59485,59489,59501,59503,59549,59555,59559,59562,59583,59589,59593,59596,59598,59602,59606,59612,59615,59619,59622,59626,59649,59652,59671,59675,59687,59690,59694,59697,59709,59712,59714,59718,59722,59733,59737,59740,59768,59772,59775,59777,59783,59785,59789,59793,59796,59833,59836,59845,59849,59852,59856,59866,59868,59870,59873,59896,59899,59901,59903,59907,59910,59914,59917,59921,59927,59931,59934,59938,59944,59946,59948,59953,59958,59963,59969,59974,59976,59978,59986,59995,59999,60002,60006,60015,60022,60028,60032,60035,60039,60042],[11,58628,14],{"id":13},[16,58630,58631,58632,58634],{},"Connecting Django to PostgreSQL in production is more than changing the ",[20,58633,10632],{}," setting. You need a database user with the right privileges, a supported PostgreSQL driver in the deploy environment, secure secret handling, correct SSL settings where required, a safe migration sequence, and a way to verify that production traffic is actually hitting PostgreSQL.",[16,58636,58637,58638,58641],{},"This guide shows a practical ",[1226,58639,58640],{},"Django PostgreSQL production setup"," for Linux servers and container-based deployments. It focuses on connection settings, secret handling, SSL, migrations, verification, and rollback planning.",[16,58643,58644],{},"If you are replacing SQLite in an existing app, there is an extra risk: creating the PostgreSQL schema is not the same as moving existing data. This guide calls that out explicitly so you do not cut over production with an empty database.",[11,58646,30],{"id":29},[16,58648,58649],{},"To connect Django to PostgreSQL in production safely:",[1173,58651,58652,58655,58658,58664,58667,58670,58673,58679],{},[66,58653,58654],{},"Create a dedicated PostgreSQL database and app user.",[66,58656,58657],{},"Install a supported PostgreSQL driver in the deploy environment.",[66,58659,58660,58661,58663],{},"Configure Django ",[20,58662,10632],{}," to use PostgreSQL explicitly.",[66,58665,58666],{},"Load credentials from environment variables or a secrets manager.",[66,58668,58669],{},"Enable SSL settings that match your provider, preferably with certificate verification for remote databases.",[66,58671,58672],{},"Test connectivity from the same environment your app server uses.",[66,58674,58675,58676,58678],{},"Run migrations carefully, knowing that ",[20,58677,10296],{}," creates schema but does not transfer existing SQLite data.",[66,58680,58681],{},"Restart the app server and verify real reads and writes.",[23099,58683],{},[11,58685,43],{"id":42},[11,58687,58689],{"id":58688},"step-1-prepare-postgresql-for-production-use","Step 1 — Prepare PostgreSQL for production use",[52,58691,58693],{"id":58692},"create-the-production-database-and-application-user","Create the production database and application user",[16,58695,58696],{},"Create a dedicated database and role for the app instead of using the PostgreSQL superuser.",[106,58698,58700],{"className":11064,"code":58699,"language":11066,"meta":111,"style":111},"CREATE DATABASE myapp_prod;\nCREATE USER myapp_user WITH PASSWORD 'replace-with-strong-password';\nGRANT CONNECT ON DATABASE myapp_prod TO myapp_user;\n",[20,58701,58702,58713,58731],{"__ignoreMap":111},[115,58703,58704,58706,58708,58711],{"class":117,"line":118},[115,58705,14611],{"class":121},[115,58707,14614],{"class":121},[115,58709,58710],{"class":262}," myapp_prod",[115,58712,3811],{"class":125},[115,58714,58715,58717,58719,58722,58724,58726,58729],{"class":117,"line":136},[115,58716,14611],{"class":121},[115,58718,14625],{"class":121},[115,58720,58721],{"class":262}," myapp_user",[115,58723,14631],{"class":121},[115,58725,14634],{"class":121},[115,58727,58728],{"class":132}," 'replace-with-strong-password'",[115,58730,3811],{"class":125},[115,58732,58733,58735,58738,58741,58743,58746,58748],{"class":117,"line":149},[115,58734,14709],{"class":121},[115,58736,58737],{"class":121}," CONNECT",[115,58739,58740],{"class":121}," ON",[115,58742,14614],{"class":121},[115,58744,58745],{"class":125}," myapp_prod ",[115,58747,14659],{"class":121},[115,58749,58750],{"class":125}," myapp_user;\n",[16,58752,58753,58754,58757],{},"If you use the default ",[20,58755,58756],{},"public"," schema, connect to the database and grant schema privileges:",[106,58759,58761],{"className":11064,"code":58760,"language":11066,"meta":111,"style":111},"\\c myapp_prod\n\nGRANT USAGE, CREATE ON SCHEMA public TO myapp_user;\n",[20,58762,58763,58768,58772],{"__ignoreMap":111},[115,58764,58765],{"class":117,"line":118},[115,58766,58767],{"class":125},"\\c myapp_prod\n",[115,58769,58770],{"class":117,"line":136},[115,58771,310],{"emptyLinePlaceholder":309},[115,58773,58774,58776,58779,58781,58783,58786,58789,58791],{"class":117,"line":149},[115,58775,14709],{"class":121},[115,58777,58778],{"class":125}," USAGE, ",[115,58780,14611],{"class":121},[115,58782,58740],{"class":121},[115,58784,58785],{"class":121}," SCHEMA",[115,58787,58788],{"class":125}," public ",[115,58790,14659],{"class":121},[115,58792,58750],{"class":125},[16,58794,58795],{},"For many Django deployments, making the app user the owner of the database and the objects it creates is simpler operationally than managing granular DDL privileges manually.",[106,58797,58799],{"className":11064,"code":58798,"language":11066,"meta":111,"style":111},"ALTER DATABASE myapp_prod OWNER TO myapp_user;\n",[20,58800,58801],{"__ignoreMap":111},[115,58802,58803,58805,58807,58809,58812,58815],{"class":117,"line":118},[115,58804,14644],{"class":121},[115,58806,14614],{"class":121},[115,58808,58745],{"class":125},[115,58810,58811],{"class":121},"OWNER",[115,58813,58814],{"class":121}," TO",[115,58816,58750],{"class":125},[16,58818,58819],{},"That avoids common migration failures caused by missing permissions for schema changes.",[52,58821,58823],{"id":58822},"grant-only-the-required-privileges","Grant only the required privileges",[16,58825,58826],{},"Use a dedicated role per app and avoid reusing an admin account. The app usually needs to read, write, and run migrations, but it should not have broader cluster-level privileges than necessary.",[52,58828,58830],{"id":58829},"confirm-host-port-database-name-username-and-ssl-requirements","Confirm host, port, database name, username, and SSL requirements",[16,58832,58833],{},"Before changing Django settings, confirm:",[63,58835,58836,58839,58845,58847,58849,58851,58854],{},[66,58837,58838],{},"database host",[66,58840,58841,58842],{},"port, usually ",[20,58843,58844],{},"5432",[66,58846,10386],{},[66,58848,10389],{},[66,58850,10392],{},[66,58852,58853],{},"whether SSL is required",[66,58855,58856],{},"whether your provider gives you a CA certificate for verification",[16,58858,58859],{},"This matters especially for managed PostgreSQL services, which often require TLS and may expect certificate validation.",[16,58861,58862],{},[1226,58863,36435],{},[16,58865,58866],{},"From the app host, confirm basic TCP reachability:",[106,58868,58870],{"className":108,"code":58869,"language":110,"meta":111,"style":111},"nc -zv DB_HOST 5432\n",[20,58871,58872],{"__ignoreMap":111},[115,58873,58874,58876,58878,58880],{"class":117,"line":118},[115,58875,5352],{"class":262},[115,58877,5355],{"class":202},[115,58879,167],{"class":132},[115,58881,47178],{"class":202},[16,58883,58884],{},"This only confirms that the port is reachable. It does not prove PostgreSQL authentication, SSL, or Django settings are correct.",[16,58886,58887,58888,58891],{},"If it fails, check firewall rules, security groups, PostgreSQL ",[20,58889,58890],{},"listen_addresses",", and host-based access rules.",[23099,58893],{},[11,58895,58897],{"id":58896},"step-2-install-the-postgresql-driver-in-django","Step 2 — Install the PostgreSQL driver in Django",[52,58899,58901,58902,4493,58904,58906],{"id":58900},"choose-psycopg-or-psycopg2-binary-appropriately","Choose ",[20,58903,10542],{},[20,58905,10587],{}," appropriately",[16,58908,58909,58910,58912,58913,58915],{},"For current Django deployments, prefer ",[20,58911,10542],{}," version 3 if your stack supports it. It is the modern PostgreSQL adapter. ",[20,58914,10587],{}," is still widely used and supported in many deployments.",[16,58917,58918],{},"Install one of these, not both unless you have a specific reason.",[106,58920,58922],{"className":108,"code":58921,"language":110,"meta":111,"style":111},"pip install \"psycopg[binary]\"\n",[20,58923,58924],{"__ignoreMap":111},[115,58925,58926,58928,58930],{"class":117,"line":118},[115,58927,8618],{"class":262},[115,58929,6600],{"class":132},[115,58931,10561],{"class":132},[16,58933,3488],{},[106,58935,58937],{"className":108,"code":58936,"language":110,"meta":111,"style":111},"pip install psycopg2-binary\n",[20,58938,58939],{"__ignoreMap":111},[115,58940,58941,58943,58945],{"class":117,"line":118},[115,58942,8618],{"class":262},[115,58944,6600],{"class":132},[115,58946,58947],{"class":132}," psycopg2-binary\n",[52,58949,58951],{"id":58950},"add-the-dependency-to-requirements-and-rebuild-the-environment","Add the dependency to requirements and rebuild the environment",[106,58953,58955],{"className":108,"code":58954,"language":110,"meta":111,"style":111},"pip freeze > requirements.txt\n",[20,58956,58957],{"__ignoreMap":111},[115,58958,58959,58961,58963,58965],{"class":117,"line":118},[115,58960,8618],{"class":262},[115,58962,16323],{"class":132},[115,58964,604],{"class":121},[115,58966,12353],{"class":132},[16,58968,58969],{},"If you deploy with Docker, rebuild the image. If you deploy to a virtualenv on a server, reinstall dependencies there.",[52,58971,58973],{"id":58972},"verify-the-driver-imports-successfully","Verify the driver imports successfully",[106,58975,58977],{"className":108,"code":58976,"language":110,"meta":111,"style":111},"python -c \"import psycopg; print('psycopg ok')\"\n",[20,58978,58979],{"__ignoreMap":111},[115,58980,58981,58983,58985],{"class":117,"line":118},[115,58982,1114],{"class":262},[115,58984,1024],{"class":202},[115,58986,10604],{"class":132},[16,58988,58989],{},"Or for psycopg2:",[106,58991,58993],{"className":108,"code":58992,"language":110,"meta":111,"style":111},"python -c \"import psycopg2; print('psycopg2 ok')\"\n",[20,58994,58995],{"__ignoreMap":111},[115,58996,58997,58999,59001],{"class":117,"line":118},[115,58998,1114],{"class":262},[115,59000,1024],{"class":202},[115,59002,10618],{"class":132},[16,59004,59005],{},[1226,59006,36435],{},[16,59008,59009],{},"Run the import test in the same environment your app server uses. A successful local import does not help if Gunicorn or Uvicorn runs from a different virtualenv, image, or container.",[23099,59011],{},[11,59013,59015],{"id":59014},"step-3-configure-django-database-settings-for-postgresql","Step 3 — Configure Django database settings for PostgreSQL",[52,59017,59019,59020,59022],{"id":59018},"replace-sqlite-defaults-with-a-postgresql-databases-configuration","Replace SQLite defaults with a PostgreSQL ",[20,59021,10632],{}," configuration",[16,59024,59025],{},"In production settings, switch the engine to PostgreSQL explicitly.",[106,59027,59029],{"className":2369,"code":59028,"language":1114,"meta":111,"style":111},"import os\n\ndb_options = {}\ndb_sslmode = os.environ.get(\"DB_SSLMODE\")\ndb_sslrootcert = os.environ.get(\"DB_SSLROOTCERT\")\n\nif db_sslmode:\n    db_options[\"sslmode\"] = db_sslmode\nif db_sslrootcert:\n    db_options[\"sslrootcert\"] = db_sslrootcert\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": os.environ[\"DB_NAME\"],\n        \"USER\": os.environ[\"DB_USER\"],\n        \"PASSWORD\": os.environ[\"DB_PASSWORD\"],\n        \"HOST\": os.environ[\"DB_HOST\"],\n        \"PORT\": os.environ.get(\"DB_PORT\", \"5432\"),\n        \"CONN_MAX_AGE\": int(os.environ.get(\"DB_CONN_MAX_AGE\", \"60\")),\n        \"CONN_HEALTH_CHECKS\": True,\n        \"OPTIONS\": db_options,\n    }\n}\n",[20,59030,59031,59037,59041,59050,59063,59076,59080,59087,59102,59109,59122,59126,59134,59140,59150,59160,59170,59180,59190,59204,59222,59233,59240,59244],{"__ignoreMap":111},[115,59032,59033,59035],{"class":117,"line":118},[115,59034,5613],{"class":121},[115,59036,5616],{"class":125},[115,59038,59039],{"class":117,"line":136},[115,59040,310],{"emptyLinePlaceholder":309},[115,59042,59043,59046,59048],{"class":117,"line":149},[115,59044,59045],{"class":125},"db_options ",[115,59047,129],{"class":121},[115,59049,54396],{"class":125},[115,59051,59052,59055,59057,59059,59061],{"class":117,"line":162},[115,59053,59054],{"class":125},"db_sslmode ",[115,59056,129],{"class":121},[115,59058,8884],{"class":125},[115,59060,10787],{"class":132},[115,59062,2394],{"class":125},[115,59064,59065,59068,59070,59072,59074],{"class":117,"line":175},[115,59066,59067],{"class":125},"db_sslrootcert ",[115,59069,129],{"class":121},[115,59071,8884],{"class":125},[115,59073,10826],{"class":132},[115,59075,2394],{"class":125},[115,59077,59078],{"class":117,"line":350},[115,59079,310],{"emptyLinePlaceholder":309},[115,59081,59082,59084],{"class":117,"line":365},[115,59083,10833],{"class":121},[115,59085,59086],{"class":125}," db_sslmode:\n",[115,59088,59089,59092,59095,59097,59099],{"class":117,"line":380},[115,59090,59091],{"class":125},"    db_options[",[115,59093,59094],{"class":132},"\"sslmode\"",[115,59096,10861],{"class":125},[115,59098,129],{"class":121},[115,59100,59101],{"class":125}," db_sslmode\n",[115,59103,59104,59106],{"class":117,"line":487},[115,59105,10833],{"class":121},[115,59107,59108],{"class":125}," db_sslrootcert:\n",[115,59110,59111,59113,59115,59117,59119],{"class":117,"line":2095},[115,59112,59091],{"class":125},[115,59114,10858],{"class":132},[115,59116,10861],{"class":125},[115,59118,129],{"class":121},[115,59120,59121],{"class":125}," db_sslrootcert\n",[115,59123,59124],{"class":117,"line":2104},[115,59125,310],{"emptyLinePlaceholder":309},[115,59127,59128,59130,59132],{"class":117,"line":2113},[115,59129,10632],{"class":202},[115,59131,2380],{"class":121},[115,59133,2166],{"class":125},[115,59135,59136,59138],{"class":117,"line":2122},[115,59137,10664],{"class":132},[115,59139,3374],{"class":125},[115,59141,59142,59144,59146,59148],{"class":117,"line":2131},[115,59143,10671],{"class":132},[115,59145,2513],{"class":125},[115,59147,10676],{"class":132},[115,59149,3354],{"class":125},[115,59151,59152,59154,59156,59158],{"class":117,"line":2136},[115,59153,10683],{"class":132},[115,59155,10686],{"class":125},[115,59157,10689],{"class":132},[115,59159,3430],{"class":125},[115,59161,59162,59164,59166,59168],{"class":117,"line":2142},[115,59163,10696],{"class":132},[115,59165,10686],{"class":125},[115,59167,10701],{"class":132},[115,59169,3430],{"class":125},[115,59171,59172,59174,59176,59178],{"class":117,"line":2273},[115,59173,10708],{"class":132},[115,59175,10686],{"class":125},[115,59177,10713],{"class":132},[115,59179,3430],{"class":125},[115,59181,59182,59184,59186,59188],{"class":117,"line":2282},[115,59183,10720],{"class":132},[115,59185,10686],{"class":125},[115,59187,10725],{"class":132},[115,59189,3430],{"class":125},[115,59191,59192,59194,59196,59198,59200,59202],{"class":117,"line":2291},[115,59193,10732],{"class":132},[115,59195,10735],{"class":125},[115,59197,10738],{"class":132},[115,59199,1153],{"class":125},[115,59201,10743],{"class":132},[115,59203,10746],{"class":125},[115,59205,59206,59208,59210,59212,59214,59216,59218,59220],{"class":117,"line":2299},[115,59207,10751],{"class":132},[115,59209,2513],{"class":125},[115,59211,10756],{"class":202},[115,59213,10759],{"class":125},[115,59215,10762],{"class":132},[115,59217,1153],{"class":125},[115,59219,10767],{"class":132},[115,59221,10770],{"class":125},[115,59223,59224,59227,59229,59231],{"class":117,"line":2307},[115,59225,59226],{"class":132},"        \"CONN_HEALTH_CHECKS\"",[115,59228,2513],{"class":125},[115,59230,35949],{"class":202},[115,59232,3354],{"class":125},[115,59234,59235,59237],{"class":117,"line":2315},[115,59236,10775],{"class":132},[115,59238,59239],{"class":125},": db_options,\n",[115,59241,59242],{"class":117,"line":2320},[115,59243,2233],{"class":125},[115,59245,59246],{"class":117,"line":7083},[115,59247,2323],{"class":125},[52,59249,59251],{"id":59250},"read-credentials-from-environment-variables","Read credentials from environment variables",[16,59253,59254,59255,59257],{},"Do not hardcode credentials in ",[20,59256,10342],{},". A simple environment contract looks like this:",[106,59259,59261],{"className":21525,"code":59260,"language":21527,"meta":111,"style":111},"DB_NAME=myapp_prod\nDB_USER=myapp_user\nDB_PASSWORD=replace-me\nDB_HOST=10.0.0.15\nDB_PORT=5432\nDB_SSLMODE=verify-full\nDB_SSLROOTCERT=\u002Fetc\u002Fssl\u002Fcerts\u002Fmy-postgres-ca.pem\nDJANGO_SETTINGS_MODULE=config.settings.production\n",[20,59262,59263,59268,59273,59277,59282,59286,59291,59296],{"__ignoreMap":111},[115,59264,59265],{"class":117,"line":118},[115,59266,59267],{},"DB_NAME=myapp_prod\n",[115,59269,59270],{"class":117,"line":136},[115,59271,59272],{},"DB_USER=myapp_user\n",[115,59274,59275],{"class":117,"line":149},[115,59276,12444],{},[115,59278,59279],{"class":117,"line":162},[115,59280,59281],{},"DB_HOST=10.0.0.15\n",[115,59283,59284],{"class":117,"line":175},[115,59285,12454],{},[115,59287,59288],{"class":117,"line":350},[115,59289,59290],{},"DB_SSLMODE=verify-full\n",[115,59292,59293],{"class":117,"line":365},[115,59294,59295],{},"DB_SSLROOTCERT=\u002Fetc\u002Fssl\u002Fcerts\u002Fmy-postgres-ca.pem\n",[115,59297,59298],{"class":117,"line":380},[115,59299,2338],{},[16,59301,59302,59303,59305],{},"Do not commit ",[20,59304,191],{}," files with real credentials to version control.",[52,59307,59309,59310,59312],{"id":59308},"set-connection-options-such-as-conn_max_age-health-checks-and-ssl-mode","Set connection options such as ",[20,59311,10933],{},", health checks, and SSL mode",[16,59314,59315],{},"If the PostgreSQL server is remote or managed, use the SSL settings required by your provider. Over untrusted networks, prefer certificate verification over encryption without verification.",[63,59317,59318,59323],{},[66,59319,59320,59322],{},[20,59321,10282],{}," encrypts the connection but does not verify server identity.",[66,59324,59325,37824,59328,59331],{},[20,59326,59327],{},"sslmode=verify-full",[20,59329,59330],{},"sslrootcert"," is stronger when your provider supplies a CA certificate.",[16,59333,59334],{},"If your provider documents a different SSL combination, follow that documentation.",[52,59336,59338],{"id":59337},"separate-development-and-production-settings-cleanly","Separate development and production settings cleanly",[16,59340,59341],{},"Keep SQLite in development if that fits your workflow, but isolate it from production settings. Do not leave fallback logic that silently uses SQLite in production when env vars are missing.",[16,59343,59344],{},[1226,59345,36435],{},[16,59347,33361],{},[106,59349,59350],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,59351,59352],{"__ignoreMap":111},[115,59353,59354,59356,59358,59360],{"class":117,"line":118},[115,59355,1114],{"class":262},[115,59357,1117],{"class":132},[115,59359,1814],{"class":132},[115,59361,1817],{"class":202},[23099,59363],{},[11,59365,59367],{"id":59366},"step-4-store-secrets-safely-in-production","Step 4 — Store secrets safely in production",[52,59369,59371],{"id":59370},"use-environment-variables-or-a-secrets-manager","Use environment variables or a secrets manager",[16,59373,59374,59375,59377],{},"For a Linux server with ",[20,59376,1277],{},", secrets are commonly injected through an environment file or service environment settings. For containers, use runtime environment variables or your orchestrator’s secret mechanism.",[16,59379,9761,59380,59382],{},[20,59381,1277],{}," snippet:",[106,59384,59385],{"className":2026,"code":46951,"language":2028,"meta":111,"style":111},[20,59386,59387,59391],{"__ignoreMap":111},[115,59388,59389],{"class":117,"line":118},[115,59390,2060],{"class":262},[115,59392,59393,59395],{"class":117,"line":136},[115,59394,2089],{"class":121},[115,59396,4912],{"class":125},[16,59398,59399],{},"Make sure that file is readable only by the appropriate account or group:",[106,59401,59403],{"className":108,"code":59402,"language":110,"meta":111,"style":111},"sudo chown root:myapp \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chmod 640 \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[20,59404,59405,59416],{"__ignoreMap":111},[115,59406,59407,59409,59411,59414],{"class":117,"line":118},[115,59408,2001],{"class":262},[115,59410,6733],{"class":132},[115,59412,59413],{"class":132}," root:myapp",[115,59415,23352],{"class":132},[115,59417,59418,59420,59422,59424],{"class":117,"line":136},[115,59419,2001],{"class":262},[115,59421,12480],{"class":132},[115,59423,12483],{"class":202},[115,59425,23352],{"class":132},[16,59427,59428],{},"Adjust the group to match the user or group your service actually runs as.",[52,59430,59432],{"id":59431},"avoid-committing-database-credentials-to-the-repository","Avoid committing database credentials to the repository",[16,59434,59435],{},"Keep secrets out of:",[63,59437,59438,59442,59448,59451],{},[66,59439,59440],{},[20,59441,10342],{},[66,59443,59444,59445,59447],{},"committed ",[20,59446,191],{}," files",[66,59449,59450],{},"CI logs",[66,59452,59453],{},"shell history where possible",[52,59455,59457],{"id":59456},"file-permission-and-process-environment-considerations","File permission and process environment considerations",[16,59459,59460],{},"Anyone who can read the deployed environment can often read database credentials. Limit shell access on the host, lock down secret files, and confirm the process manager loads the same environment that you test manually.",[23099,59462],{},[11,59464,59466],{"id":59465},"step-5-test-the-database-connection-before-deployment","Step 5 — Test the database connection before deployment",[52,59468,59470],{"id":59469},"run-django-system-checks","Run Django system checks",[106,59472,59473],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,59474,59475],{"__ignoreMap":111},[115,59476,59477,59479,59481,59483],{"class":117,"line":118},[115,59478,1114],{"class":262},[115,59480,1117],{"class":132},[115,59482,1814],{"class":132},[115,59484,1817],{"class":202},[52,59486,59488],{"id":59487},"open-a-django-shell-and-verify-a-live-database-query","Open a Django shell and verify a live database query",[106,59490,59491],{"className":108,"code":6059,"language":110,"meta":111,"style":111},[20,59492,59493],{"__ignoreMap":111},[115,59494,59495,59497,59499],{"class":117,"line":118},[115,59496,1114],{"class":262},[115,59498,1117],{"class":132},[115,59500,6070],{"class":132},[16,59502,5144],{},[106,59504,59506],{"className":2369,"code":59505,"language":1114,"meta":111,"style":111},"from django.db import connection\nprint(connection.vendor)\nwith connection.cursor() as cursor:\n    cursor.execute(\"SELECT current_database(), current_user;\")\n    print(cursor.fetchone())\n",[20,59507,59508,59518,59525,59535,59543],{"__ignoreMap":111},[115,59509,59510,59512,59514,59516],{"class":117,"line":118},[115,59511,5621],{"class":121},[115,59513,11218],{"class":125},[115,59515,5613],{"class":121},[115,59517,11223],{"class":125},[115,59519,59520,59522],{"class":117,"line":136},[115,59521,6102],{"class":202},[115,59523,59524],{"class":125},"(connection.vendor)\n",[115,59526,59527,59529,59531,59533],{"class":117,"line":149},[115,59528,11232],{"class":121},[115,59530,11235],{"class":125},[115,59532,5719],{"class":121},[115,59534,11240],{"class":125},[115,59536,59537,59539,59541],{"class":117,"line":162},[115,59538,11245],{"class":125},[115,59540,11248],{"class":132},[115,59542,2394],{"class":125},[115,59544,59545,59547],{"class":117,"line":175},[115,59546,11255],{"class":202},[115,59548,11258],{"class":125},[16,59550,59551,59552,59554],{},"You should see ",[20,59553,1558],{}," as the vendor and the expected database and user.",[52,59556,59558],{"id":59557},"check-application-startup-logs-for-connection-errors","Check application startup logs for connection errors",[16,59560,59561],{},"After updating environment or settings, inspect app server logs before routing traffic:",[106,59563,59565],{"className":108,"code":59564,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 100 --no-pager\n",[20,59566,59567],{"__ignoreMap":111},[115,59568,59569,59571,59573,59575,59577,59579,59581],{"class":117,"line":118},[115,59570,2001],{"class":262},[115,59572,5030],{"class":132},[115,59574,2788],{"class":202},[115,59576,2791],{"class":132},[115,59578,2794],{"class":202},[115,59580,2797],{"class":202},[115,59582,2800],{"class":202},[16,59584,59585,59586,59588],{},"Use your actual service name if it is not ",[20,59587,14954],{},". In containers, use the equivalent container log command.",[16,59590,59591],{},[1226,59592,4956],{},[16,59594,59595],{},"Do not remove the old release or old environment file until the new process starts successfully and database connectivity is confirmed.",[23099,59597],{},[11,59599,59601],{"id":59600},"step-6-run-migrations-safely-in-production","Step 6 — Run migrations safely in production",[52,59603,59605],{"id":59604},"important-schema-migration-is-not-data-migration","Important: schema migration is not data migration",[16,59607,59608,59609,59611],{},"If you are switching an existing app from SQLite to PostgreSQL, ",[20,59610,38156],{}," creates the PostgreSQL schema but does not copy data from SQLite.",[16,59613,59614],{},"You need a separate export\u002Fimport or data migration plan before cutting over production traffic. Keep the old database available until the new PostgreSQL-backed deployment is verified.",[52,59616,59618],{"id":59617},"back-up-the-current-database-before-schema-changes","Back up the current database before schema changes",[16,59620,59621],{},"Before production migrations, take a backup or create a managed snapshot. This matters for normal PostgreSQL schema changes and also for any cutover where you are moving from SQLite or another source database.",[52,59623,59625],{"id":59624},"apply-migrations-with-the-correct-settings-module","Apply migrations with the correct settings module",[106,59627,59629],{"className":108,"code":59628,"language":110,"meta":111,"style":111},"python manage.py showmigrations\npython manage.py migrate --noinput\n",[20,59630,59631,59639],{"__ignoreMap":111},[115,59632,59633,59635,59637],{"class":117,"line":118},[115,59634,1114],{"class":262},[115,59636,1117],{"class":132},[115,59638,1129],{"class":132},[115,59640,59641,59643,59645,59647],{"class":117,"line":136},[115,59642,1114],{"class":262},[115,59644,1117],{"class":132},[115,59646,1826],{"class":132},[115,59648,1841],{"class":202},[16,59650,59651],{},"If you use an explicit settings module:",[106,59653,59655],{"className":108,"code":59654,"language":110,"meta":111,"style":111},"python manage.py migrate --noinput --settings=config.settings.production\n",[20,59656,59657],{"__ignoreMap":111},[115,59658,59659,59661,59663,59665,59668],{"class":117,"line":118},[115,59660,1114],{"class":262},[115,59662,1117],{"class":132},[115,59664,1826],{"class":132},[115,59666,59667],{"class":202}," --noinput",[115,59669,59670],{"class":202}," --settings=config.settings.production\n",[52,59672,59674],{"id":59673},"verify-migration-status-after-deployment","Verify migration status after deployment",[106,59676,59677],{"className":108,"code":11330,"language":110,"meta":111,"style":111},[20,59678,59679],{"__ignoreMap":111},[115,59680,59681,59683,59685],{"class":117,"line":118},[115,59682,1114],{"class":262},[115,59684,1117],{"class":132},[115,59686,1129],{"class":132},[16,59688,59689],{},"All expected migrations should be marked as applied.",[52,59691,59693],{"id":59692},"notes-on-zero-downtime-and-migration-ordering","Notes on zero-downtime and migration ordering",[16,59695,59696],{},"For safer releases, use backward-compatible migrations where possible:",[1173,59698,59699,59702,59704,59707],{},[66,59700,59701],{},"deploy code that is compatible with both old and new schema",[66,59703,1186],{},[66,59705,59706],{},"restart app processes",[66,59708,58162],{},[16,59710,59711],{},"If a migration is destructive or not backward-compatible, use an explicit multi-step rollout instead of a simple in-place deploy.",[23099,59713],{},[11,59715,59717],{"id":59716},"step-7-integrate-with-the-app-server-and-release-process","Step 7 — Integrate with the app server and release process",[52,59719,59721],{"id":59720},"ensure-gunicorn-or-uvicorn-inherits-the-correct-environment","Ensure Gunicorn or Uvicorn inherits the correct environment",[16,59723,59724,59725,59728,59729,59732],{},"If the app server does not receive ",[20,59726,59727],{},"DB_*"," variables, Django may fail with ",[20,59730,59731],{},"ImproperlyConfigured"," or connection errors. Confirm the process manager loads the same environment you tested manually.",[52,59734,59736],{"id":59735},"order-of-operations-in-a-release","Order of operations in a release",[16,59738,59739],{},"A safe release sequence is:",[1173,59741,59742,59745,59748,59751,59754,59757,59760,59762,59765],{},[66,59743,59744],{},"upload new code",[66,59746,59747],{},"install dependencies",[66,59749,59750],{},"load updated environment",[66,59752,59753],{},"run checks",[66,59755,59756],{},"verify PostgreSQL connectivity",[66,59758,59759],{},"back up the database",[66,59761,1186],{},[66,59763,59764],{},"restart Gunicorn or Uvicorn",[66,59766,59767],{},"verify application health",[52,59769,59771],{"id":59770},"avoid-restarting-incompatible-code-against-a-changed-schema","Avoid restarting incompatible code against a changed schema",[16,59773,59774],{},"Keep code and schema compatibility in mind. If a migration is destructive, plan the rollout so old and new app versions cannot conflict.",[52,59776,11436],{"id":11435},[16,59778,59779,59780,59782],{},"Once you are repeating the same environment validation, backup trigger, migration commands, and service restart on every release, this workflow is a good candidate for a script or CI\u002FCD job. A reusable template is especially useful for validating required ",[20,59781,59727],{}," variables and enforcing release order before the app restarts.",[23099,59784],{},[11,59786,59788],{"id":59787},"step-8-verify-the-production-setup","Step 8 — Verify the production setup",[52,59790,59792],{"id":59791},"confirm-django-is-using-postgresql-instead-of-sqlite","Confirm Django is using PostgreSQL instead of SQLite",[16,59794,59795],{},"In a Django shell:",[106,59797,59799],{"className":2369,"code":59798,"language":1114,"meta":111,"style":111},"from django.conf import settings\nprint(settings.DATABASES[\"default\"][\"ENGINE\"])\n",[20,59800,59801,59813],{"__ignoreMap":111},[115,59802,59803,59805,59808,59810],{"class":117,"line":118},[115,59804,5621],{"class":121},[115,59806,59807],{"class":125}," django.conf ",[115,59809,5613],{"class":121},[115,59811,59812],{"class":125}," settings\n",[115,59814,59815,59817,59820,59822,59824,59826,59828,59831],{"class":117,"line":136},[115,59816,6102],{"class":202},[115,59818,59819],{"class":125},"(settings.",[115,59821,10632],{"class":202},[115,59823,10844],{"class":125},[115,59825,10847],{"class":132},[115,59827,10850],{"class":125},[115,59829,59830],{"class":132},"\"ENGINE\"",[115,59832,53835],{"class":125},[16,59834,59835],{},"It should print:",[106,59837,59839],{"className":2369,"code":59838,"language":1114,"meta":111,"style":111},"django.db.backends.postgresql\n",[20,59840,59841],{"__ignoreMap":111},[115,59842,59843],{"class":117,"line":118},[115,59844,59838],{"class":125},[52,59846,59848],{"id":59847},"validate-read-and-write-behavior-through-the-app","Validate read and write behavior through the app",[16,59850,59851],{},"Test a real application path that reads from and writes to the database. Do not rely only on startup success.",[52,59853,59855],{"id":59854},"check-connection-stability","Check connection stability",[16,59857,59858,59859,59861,59862,59865],{},"If you set ",[20,59860,10933],{},", monitor for connection drops or stale connections in logs. ",[20,59863,59864],{},"CONN_HEALTH_CHECKS=True"," helps in environments where connections may be interrupted by failover or idle timeout behavior.",[23099,59867],{},[11,59869,1321],{"id":1320},[16,59871,59872],{},"This setup works because it separates deployment concerns clearly:",[63,59874,59875,59878,59881,59884,59887,59890,59893],{},[66,59876,59877],{},"Django uses PostgreSQL through a supported adapter.",[66,59879,59880],{},"Secrets are injected at runtime instead of stored in code.",[66,59882,59883],{},"Production settings are explicit, so SQLite cannot be used accidentally.",[66,59885,59886],{},"SSL options are configurable for managed or remote PostgreSQL.",[66,59888,59889],{},"Connectivity is verified before the app is restarted.",[66,59891,59892],{},"Migrations are treated as a release step with backup and rollback planning.",[66,59894,59895],{},"Data movement is handled separately from schema creation when moving off SQLite.",[16,59897,59898],{},"Use a dedicated app role to reduce blast radius if credentials leak. Use verified TLS settings for remote databases when your provider supports them. Keep production settings isolated so the deploy fails fast if required database variables are missing.",[23099,59900],{},[11,59902,10095],{"id":10094},[52,59904,59906],{"id":59905},"switching-from-sqlite-to-postgresql","Switching from SQLite to PostgreSQL",[16,59908,59909],{},"This page covers connection and cutover safety, not full data migration tooling. If your production app already has data in SQLite, do not assume Django migrations will move it. Plan and test a separate export\u002Fimport process, verify row counts and critical records, and keep the old database available until cutover is complete.",[52,59911,59913],{"id":59912},"connecting-to-managed-postgresql-providers","Connecting to managed PostgreSQL providers",[16,59915,59916],{},"Managed services often require SSL and may use DNS endpoints that change during failover. Use the provider’s recommended connection settings and CA certificate path if certificate verification is supported.",[52,59918,59920],{"id":59919},"using-unix-sockets-vs-tcp-host-connections","Using Unix sockets vs TCP host connections",[16,59922,59923,59924,59926],{},"If PostgreSQL runs on the same host, Unix sockets can work well. In that case, ",[20,59925,47853],{}," may be a socket directory rather than an IP or hostname. For remote databases, use TCP.",[52,59928,59930],{"id":59929},"handling-firewall-or-security-group-restrictions","Handling firewall or security group restrictions",[16,59932,59933],{},"If Django cannot connect, check host reachability first, then PostgreSQL listen settings, then access rules. Many database connection failures are really network policy issues.",[52,59935,59937],{"id":59936},"missing-env-vars-and-startup-ordering","Missing env vars and startup ordering",[16,59939,59940,59941,59943],{},"Watch for startup failures caused by missing env vars in ",[20,59942,1277],{},", containers starting before secrets are mounted, or old processes keeping stale credentials after secret rotation.",[23099,59945],{},[11,59947,1386],{"id":1385},[16,59949,44637,59950,211],{},[1395,59951,59952],{"href":3006},"Django production settings best practices",[16,59954,59955,59956,211],{},"To complete the web stack after database setup, follow ",[1395,59957,2986],{"href":2985},[16,59959,59960,59961,211],{},"For an ASGI deployment path, see ",[1395,59962,8039],{"href":8038},[16,59964,59965,59966,211],{},"For safer releases around schema changes, read ",[1395,59967,59968],{"href":1409},"how to run Django migrations in production",[16,59970,59971,59972,211],{},"If the app still cannot connect, use this runbook to ",[1395,59973,11531],{"href":6333},[23099,59975],{},[11,59977,1420],{"id":1419},[52,59979,1434,59981,4493,59983,59985],{"id":59980},"should-i-use-psycopg-or-psycopg2-for-django-in-production",[20,59982,10542],{},[20,59984,10546],{}," for Django in production?",[16,59987,59988,59989,59991,59992,59994],{},"For new deployments, ",[20,59990,10542],{}," is the modern choice if it fits your Django and platform versions. ",[20,59993,10587],{}," is still common and works well in many deployments. The important part is to use one supported adapter consistently in the same environment as your app server.",[52,59996,59998],{"id":59997},"how-do-i-store-postgresql-credentials-safely-for-a-django-deployment","How do I store PostgreSQL credentials safely for a Django deployment?",[16,60000,60001],{},"Use environment variables or a secrets manager provided by your platform. Do not commit credentials to the repository or hardcode them in Django settings.",[52,60003,60005],{"id":60004},"do-i-need-ssl-between-django-and-postgresql-in-production","Do I need SSL between Django and PostgreSQL in production?",[16,60007,60008,60009,60011,60012,60014],{},"If the database is remote, managed, or on an untrusted network path, yes. Prefer certificate verification when your provider gives you a CA certificate. ",[20,60010,10282],{}," encrypts traffic, but ",[20,60013,10286],{}," with a trusted CA is stronger.",[52,60016,60018,60019,60021],{"id":60017},"does-python-managepy-migrate-move-my-existing-sqlite-data-into-postgresql","Does ",[20,60020,38156],{}," move my existing SQLite data into PostgreSQL?",[16,60023,60024,60025,60027],{},"No. ",[20,60026,10296],{}," creates and updates schema in PostgreSQL, but it does not copy existing SQLite data. If you are switching databases, plan a separate data migration or export\u002Fimport step before cutover.",[52,60029,60031],{"id":60030},"what-is-the-safest-order-for-switching-django-from-sqlite-to-postgresql-in-production","What is the safest order for switching Django from SQLite to PostgreSQL in production?",[16,60033,60034],{},"Provision PostgreSQL first, configure Django settings and secrets, verify connectivity, create the PostgreSQL schema, migrate or import application data separately, test the new environment, then cut over traffic. Keep the old database and previous release available until the new deployment is verified.",[52,60036,60038],{"id":60037},"what-should-i-back-up-before-running-django-migrations-against-postgresql","What should I back up before running Django migrations against PostgreSQL?",[16,60040,60041],{},"Back up the production PostgreSQL database or create a provider snapshot before schema changes. If you are transitioning from SQLite, preserve the SQLite database as well until the PostgreSQL deployment and imported data are fully verified.",[1485,60043,60044],{},"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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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}",{"title":111,"searchDepth":149,"depth":149,"links":60046},[60047,60048,60049,60050,60055,60061,60069,60074,60079,60086,60092,60097,60098,60105,60106],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":58688,"depth":136,"text":58689,"children":60051},[60052,60053,60054],{"id":58692,"depth":149,"text":58693},{"id":58822,"depth":149,"text":58823},{"id":58829,"depth":149,"text":58830},{"id":58896,"depth":136,"text":58897,"children":60056},[60057,60059,60060],{"id":58900,"depth":149,"text":60058},"Choose psycopg or psycopg2-binary appropriately",{"id":58950,"depth":149,"text":58951},{"id":58972,"depth":149,"text":58973},{"id":59014,"depth":136,"text":59015,"children":60062},[60063,60065,60066,60068],{"id":59018,"depth":149,"text":60064},"Replace SQLite defaults with a PostgreSQL DATABASES configuration",{"id":59250,"depth":149,"text":59251},{"id":59308,"depth":149,"text":60067},"Set connection options such as CONN_MAX_AGE, health checks, and SSL mode",{"id":59337,"depth":149,"text":59338},{"id":59366,"depth":136,"text":59367,"children":60070},[60071,60072,60073],{"id":59370,"depth":149,"text":59371},{"id":59431,"depth":149,"text":59432},{"id":59456,"depth":149,"text":59457},{"id":59465,"depth":136,"text":59466,"children":60075},[60076,60077,60078],{"id":59469,"depth":149,"text":59470},{"id":59487,"depth":149,"text":59488},{"id":59557,"depth":149,"text":59558},{"id":59600,"depth":136,"text":59601,"children":60080},[60081,60082,60083,60084,60085],{"id":59604,"depth":149,"text":59605},{"id":59617,"depth":149,"text":59618},{"id":59624,"depth":149,"text":59625},{"id":59673,"depth":149,"text":59674},{"id":59692,"depth":149,"text":59693},{"id":59716,"depth":136,"text":59717,"children":60087},[60088,60089,60090,60091],{"id":59720,"depth":149,"text":59721},{"id":59735,"depth":149,"text":59736},{"id":59770,"depth":149,"text":59771},{"id":11435,"depth":149,"text":11436},{"id":59787,"depth":136,"text":59788,"children":60093},[60094,60095,60096],{"id":59791,"depth":149,"text":59792},{"id":59847,"depth":149,"text":59848},{"id":59854,"depth":149,"text":59855},{"id":1320,"depth":136,"text":1321},{"id":10094,"depth":136,"text":10095,"children":60099},[60100,60101,60102,60103,60104],{"id":59905,"depth":149,"text":59906},{"id":59912,"depth":149,"text":59913},{"id":59919,"depth":149,"text":59920},{"id":59929,"depth":149,"text":59930},{"id":59936,"depth":149,"text":59937},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":60107},[60108,60110,60111,60112,60114,60115],{"id":59980,"depth":149,"text":60109},"Should I use psycopg or psycopg2 for Django in production?",{"id":59997,"depth":149,"text":59998},{"id":60004,"depth":149,"text":60005},{"id":60017,"depth":149,"text":60113},"Does python manage.py migrate move my existing SQLite data into PostgreSQL?",{"id":60030,"depth":149,"text":60031},{"id":60037,"depth":149,"text":60038},"Connecting Django to PostgreSQL in production is more than changing the DATABASES setting.",{},"\u002Fconnect-django-to-postgresql-production","20",[11637,60121,2992],"\u002Fdeploy\u002Fdeploy-django-on-railway",{"title":58624,"description":60116},[1557,1558],"connect-django-to-postgresql-production",[1557,1558],"evE26LsUa4VDxpGF0aq_tW3nNLwfhV1aFx_LI6D41hs",{"id":60128,"title":60129,"body":60130,"category":3088,"description":61628,"difficulty":3090,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":61629,"navigation":309,"path":61630,"priority":61631,"related":61632,"role":1553,"section":3098,"seo":61633,"stack":61634,"stem":61636,"tags":61637,"type":1561,"__hash__":61638},"articles\u002Fdeploy-django-on-railway.md","How to Deploy Django on Railway",{"type":8,"value":60131,"toc":61573},[60132,60134,60137,60140,60164,60167,60169,60172,60211,60214,60216,60220,60224,60227,60258,60264,60267,60292,60296,60299,60594,60597,60625,60628,60642,60644,60675,60679,60682,60801,60804,60818,60823,60825,60843,60846,60850,60853,60873,60879,60885,60905,60907,60919,60923,60927,60930,60941,60944,60948,60954,60957,60961,60964,60984,60987,60999,61002,61006,61010,61015,61018,61021,61025,61028,61044,61047,61051,61054,61068,61074,61078,61081,61093,61096,61099,61111,61114,61118,61122,61125,61162,61165,61178,61180,61183,61195,61198,61200,61211,61215,61217,61228,61231,61235,61238,61250,61253,61257,61261,61264,61281,61284,61298,61302,61305,61334,61338,61341,61352,61356,61360,61363,61367,61370,61376,61381,61385,61388,61405,61407,61410,61416,61419,61422,61424,61428,61431,61434,61438,61441,61469,61472,61476,61479,61483,61486,61490,61493,61495,61501,61506,61511,61517,61519,61523,61526,61530,61541,61545,61550,61557,61563,61567,61570],[11,60133,14],{"id":13},[16,60135,60136],{},"If you want to deploy Django on Railway, the main challenge is not just getting a container to start. The real job is making your app run safely in production with the right process manager, database configuration, static files, environment variables, proxy-aware security settings, and verification steps.",[16,60138,60139],{},"Common Django Railway deployment failures are predictable:",[63,60141,60142,60145,60150,60153,60156,60161],{},[66,60143,60144],{},"Gunicorn is missing, so the web process never starts",[66,60146,60147,60149],{},[20,60148,2719],{}," does not include the Railway domain",[66,60151,60152],{},"static files are not collected as part of build\u002Fdeploy",[66,60154,60155],{},"migrations are not applied in a controlled release step",[66,60157,60158,60160],{},[20,60159,24957],{}," is left enabled",[66,60162,60163],{},"secrets are committed into Git instead of stored as environment variables",[16,60165,60166],{},"Railway can be a fast way to host Django, but it does not remove the need for production-safe Django settings.",[11,60168,30],{"id":29},[16,60170,60171],{},"To deploy Django on Railway safely:",[1173,60173,60174,60177,60180,60183,60186,60197,60200,60205,60208],{},[66,60175,60176],{},"prepare production Django settings",[66,60178,60179],{},"add Gunicorn, PostgreSQL URL parsing, and static file handling",[66,60181,60182],{},"provision PostgreSQL in Railway",[66,60184,60185],{},"connect your GitHub repo to Railway",[66,60187,60188,60189,1153,60191,1153,60193,20346,60195],{},"set environment variables such as ",[20,60190,34901],{},[20,60192,10873],{},[20,60194,2719],{},[20,60196,2725],{},[66,60198,60199],{},"set a Gunicorn start command",[66,60201,7902,60202,60204],{},[20,60203,13689],{}," during the build\u002Fdeploy process",[66,60206,60207],{},"run migrations as a controlled release step",[66,60209,60210],{},"verify the site, admin, database access, and static assets",[16,60212,60213],{},"Railway is a good fit for straightforward Django deployments and smaller teams, but you still need to handle production settings correctly.",[11,60215,43],{"id":42},[11,60217,60219],{"id":60218},"_1-prepare-your-django-app-for-railway-deployment","1) Prepare your Django app for Railway deployment",[52,60221,60223],{"id":60222},"add-production-dependencies","Add production dependencies",[16,60225,60226],{},"Install the packages most Railway Django deployments need:",[106,60228,60230],{"className":108,"code":60229,"language":110,"meta":111,"style":111},"pip install gunicorn whitenoise dj-database-url psycopg[binary]\npip freeze > requirements.txt\n",[20,60231,60232,60248],{"__ignoreMap":111},[115,60233,60234,60236,60238,60240,60242,60245],{"class":117,"line":118},[115,60235,8618],{"class":262},[115,60237,6600],{"class":132},[115,60239,2791],{"class":132},[115,60241,16313],{"class":132},[115,60243,60244],{"class":132}," dj-database-url",[115,60246,60247],{"class":132}," psycopg[binary]\n",[115,60249,60250,60252,60254,60256],{"class":117,"line":136},[115,60251,8618],{"class":262},[115,60253,16323],{"class":132},[115,60255,604],{"class":121},[115,60257,12353],{"class":132},[16,60259,60260,60261,60263],{},"If you use a different dependency workflow, export a valid ",[20,60262,28328],{}," before deploy.",[16,60265,60266],{},"What these packages do:",[63,60268,60269,60274,60281,60286],{},[66,60270,60271,60273],{},[20,60272,14954],{},": production WSGI server",[66,60275,60276,60278,60279],{},[20,60277,45465],{},": parses ",[20,60280,10873],{},[66,60282,60283,60285],{},[20,60284,43557],{},": serves static files from Django when you are not using external object storage or a CDN",[66,60287,60288,60291],{},[20,60289,60290],{},"psycopg[binary]",": PostgreSQL driver",[52,60293,60295],{"id":60294},"update-django-production-settings","Update Django production settings",[16,60297,60298],{},"Use environment-driven settings. A minimal example:",[106,60300,60302],{"className":2369,"code":60301,"language":1114,"meta":111,"style":111},"import os\nfrom pathlib import Path\nimport dj_database_url\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = os.environ.get(\"DEBUG\", \"False\").lower() == \"true\"\n\nSECRET_KEY = os.environ[\"DJANGO_SECRET_KEY\"]\n\nALLOWED_HOSTS = [\n    host.strip() for host in os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\") if host.strip()\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    origin.strip()\n    for origin in os.environ.get(\"CSRF_TRUSTED_ORIGINS\", \"\").split(\",\")\n    if origin.strip()\n]\n\nDATABASES = {\n    \"default\": dj_database_url.parse(\n        os.environ[\"DATABASE_URL\"],\n        conn_max_age=600,\n        ssl_require=True,\n    )\n}\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nUSE_X_FORWARDED_HOST = True\nSECURE_SSL_REDIRECT = not DEBUG\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n",[20,60303,60304,60310,60320,60326,60330,60342,60346,60367,60371,60383,60387,60395,60426,60430,60434,60442,60447,60470,60477,60481,60485,60493,60500,60509,60519,60530,60534,60538,60542,60558,60566,60578,60586],{"__ignoreMap":111},[115,60305,60306,60308],{"class":117,"line":118},[115,60307,5613],{"class":121},[115,60309,5616],{"class":125},[115,60311,60312,60314,60316,60318],{"class":117,"line":136},[115,60313,5621],{"class":121},[115,60315,16353],{"class":125},[115,60317,5613],{"class":121},[115,60319,16358],{"class":125},[115,60321,60322,60324],{"class":117,"line":149},[115,60323,5613],{"class":121},[115,60325,16365],{"class":125},[115,60327,60328],{"class":117,"line":162},[115,60329,310],{"emptyLinePlaceholder":309},[115,60331,60332,60334,60336,60338,60340],{"class":117,"line":175},[115,60333,16374],{"class":202},[115,60335,2380],{"class":121},[115,60337,16379],{"class":125},[115,60339,16382],{"class":202},[115,60341,34339],{"class":125},[115,60343,60344],{"class":117,"line":350},[115,60345,310],{"emptyLinePlaceholder":309},[115,60347,60348,60350,60352,60354,60357,60359,60361,60363,60365],{"class":117,"line":365},[115,60349,7350],{"class":202},[115,60351,2380],{"class":121},[115,60353,8884],{"class":125},[115,60355,60356],{"class":132},"\"DEBUG\"",[115,60358,1153],{"class":125},[115,60360,25169],{"class":132},[115,60362,25172],{"class":125},[115,60364,25175],{"class":121},[115,60366,25178],{"class":132},[115,60368,60369],{"class":117,"line":380},[115,60370,310],{"emptyLinePlaceholder":309},[115,60372,60373,60375,60377,60379,60381],{"class":117,"line":487},[115,60374,2713],{"class":202},[115,60376,2380],{"class":121},[115,60378,8861],{"class":125},[115,60380,12063],{"class":132},[115,60382,2552],{"class":125},[115,60384,60385],{"class":117,"line":2095},[115,60386,310],{"emptyLinePlaceholder":309},[115,60388,60389,60391,60393],{"class":117,"line":2104},[115,60390,2719],{"class":202},[115,60392,2380],{"class":121},[115,60394,3540],{"class":125},[115,60396,60397,60400,60402,60405,60407,60409,60411,60413,60415,60417,60419,60421,60423],{"class":117,"line":2113},[115,60398,60399],{"class":125},"    host.strip() ",[115,60401,18256],{"class":121},[115,60403,60404],{"class":125}," host ",[115,60406,18262],{"class":121},[115,60408,8884],{"class":125},[115,60410,18267],{"class":132},[115,60412,1153],{"class":125},[115,60414,18272],{"class":132},[115,60416,18275],{"class":125},[115,60418,18278],{"class":132},[115,60420,18281],{"class":125},[115,60422,10833],{"class":121},[115,60424,60425],{"class":125}," host.strip()\n",[115,60427,60428],{"class":117,"line":2122},[115,60429,2552],{"class":125},[115,60431,60432],{"class":117,"line":2131},[115,60433,310],{"emptyLinePlaceholder":309},[115,60435,60436,60438,60440],{"class":117,"line":2136},[115,60437,2725],{"class":202},[115,60439,2380],{"class":121},[115,60441,3540],{"class":125},[115,60443,60444],{"class":117,"line":2142},[115,60445,60446],{"class":125},"    origin.strip()\n",[115,60448,60449,60451,60454,60456,60458,60460,60462,60464,60466,60468],{"class":117,"line":2273},[115,60450,40952],{"class":121},[115,60452,60453],{"class":125}," origin ",[115,60455,18262],{"class":121},[115,60457,8884],{"class":125},[115,60459,18307],{"class":132},[115,60461,1153],{"class":125},[115,60463,18272],{"class":132},[115,60465,18275],{"class":125},[115,60467,18278],{"class":132},[115,60469,2394],{"class":125},[115,60471,60472,60474],{"class":117,"line":2282},[115,60473,40975],{"class":121},[115,60475,60476],{"class":125}," origin.strip()\n",[115,60478,60479],{"class":117,"line":2291},[115,60480,2552],{"class":125},[115,60482,60483],{"class":117,"line":2299},[115,60484,310],{"emptyLinePlaceholder":309},[115,60486,60487,60489,60491],{"class":117,"line":2307},[115,60488,10632],{"class":202},[115,60490,2380],{"class":121},[115,60492,2166],{"class":125},[115,60494,60495,60497],{"class":117,"line":2315},[115,60496,10664],{"class":132},[115,60498,60499],{"class":125},": dj_database_url.parse(\n",[115,60501,60502,60505,60507],{"class":117,"line":2320},[115,60503,60504],{"class":125},"        os.environ[",[115,60506,16727],{"class":132},[115,60508,3430],{"class":125},[115,60510,60511,60513,60515,60517],{"class":117,"line":7083},[115,60512,16735],{"class":5680},[115,60514,129],{"class":121},[115,60516,16740],{"class":202},[115,60518,3354],{"class":125},[115,60520,60521,60524,60526,60528],{"class":117,"line":7090},[115,60522,60523],{"class":5680},"        ssl_require",[115,60525,129],{"class":121},[115,60527,35949],{"class":202},[115,60529,3354],{"class":125},[115,60531,60532],{"class":117,"line":7097},[115,60533,16748],{"class":125},[115,60535,60536],{"class":117,"line":7108},[115,60537,2323],{"class":125},[115,60539,60540],{"class":117,"line":7113},[115,60541,310],{"emptyLinePlaceholder":309},[115,60543,60544,60546,60548,60550,60552,60554,60556],{"class":117,"line":16535},[115,60545,2377],{"class":202},[115,60547,2380],{"class":121},[115,60549,2383],{"class":125},[115,60551,2386],{"class":132},[115,60553,1153],{"class":125},[115,60555,2391],{"class":132},[115,60557,2394],{"class":125},[115,60559,60560,60562,60564],{"class":117,"line":16544},[115,60561,12021],{"class":202},[115,60563,2380],{"class":121},[115,60565,2412],{"class":202},[115,60567,60568,60570,60572,60575],{"class":117,"line":16549},[115,60569,2407],{"class":202},[115,60571,2380],{"class":121},[115,60573,60574],{"class":121}," not",[115,60576,60577],{"class":202}," DEBUG\n",[115,60579,60580,60582,60584],{"class":117,"line":16555},[115,60581,2417],{"class":202},[115,60583,2380],{"class":121},[115,60585,2412],{"class":202},[115,60587,60588,60590,60592],{"class":117,"line":16564},[115,60589,2426],{"class":202},[115,60591,2380],{"class":121},[115,60593,2412],{"class":202},[16,60595,60596],{},"If your domain is fully HTTPS-only, add HSTS:",[106,60598,60599],{"className":2369,"code":25344,"language":1114,"meta":111,"style":111},[20,60600,60601,60609,60617],{"__ignoreMap":111},[115,60602,60603,60605,60607],{"class":117,"line":118},[115,60604,7440],{"class":202},[115,60606,2380],{"class":121},[115,60608,11991],{"class":202},[115,60610,60611,60613,60615],{"class":117,"line":136},[115,60612,7464],{"class":202},[115,60614,2380],{"class":121},[115,60616,2412],{"class":202},[115,60618,60619,60621,60623],{"class":117,"line":149},[115,60620,12004],{"class":202},[115,60622,2380],{"class":121},[115,60624,7355],{"class":202},[16,60626,60627],{},"Also run a deployment check locally before pushing:",[106,60629,60630],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,60631,60632],{"__ignoreMap":111},[115,60633,60634,60636,60638,60640],{"class":117,"line":118},[115,60635,1114],{"class":262},[115,60637,1117],{"class":132},[115,60639,1814],{"class":132},[115,60641,1817],{"class":202},[16,60643,3515],{},[63,60645,60646,60653,60658,60663,60668],{},[66,60647,60648,10916,60650,60652],{},[20,60649,7350],{},[20,60651,3364],{}," in Railway",[66,60654,60655,60657],{},[20,60656,2713],{}," must come from env vars",[66,60659,60660,60662],{},[20,60661,10873],{}," must point to PostgreSQL",[66,60664,60665,60667],{},[20,60666,2719],{}," must include your Railway domain and custom domain if used",[66,60669,60670,60672,60673,18700],{},[20,60671,2725],{}," must include full ",[20,60674,7733],{},[52,60676,60678],{"id":60677},"configure-static-files-for-production","Configure static files for production",[16,60680,60681],{},"If Django will serve static files directly, configure Whitenoise:",[106,60683,60685],{"className":2369,"code":60684,"language":1114,"meta":111,"style":111},"INSTALLED_APPS = [\n    # ...\n    \"django.contrib.staticfiles\",\n]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # ...\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nSTORAGES = {\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    }\n}\n",[20,60686,60687,60695,60699,60705,60709,60713,60721,60727,60733,60737,60741,60745,60753,60765,60769,60777,60783,60793,60797],{"__ignoreMap":111},[115,60688,60689,60691,60693],{"class":117,"line":118},[115,60690,18460],{"class":202},[115,60692,2380],{"class":121},[115,60694,3540],{"class":125},[115,60696,60697],{"class":117,"line":136},[115,60698,16643],{"class":3861},[115,60700,60701,60703],{"class":117,"line":149},[115,60702,18469],{"class":132},[115,60704,3354],{"class":125},[115,60706,60707],{"class":117,"line":162},[115,60708,2552],{"class":125},[115,60710,60711],{"class":117,"line":175},[115,60712,310],{"emptyLinePlaceholder":309},[115,60714,60715,60717,60719],{"class":117,"line":350},[115,60716,16617],{"class":202},[115,60718,2380],{"class":121},[115,60720,3540],{"class":125},[115,60722,60723,60725],{"class":117,"line":365},[115,60724,16627],{"class":132},[115,60726,3354],{"class":125},[115,60728,60729,60731],{"class":117,"line":380},[115,60730,16635],{"class":132},[115,60732,3354],{"class":125},[115,60734,60735],{"class":117,"line":487},[115,60736,16643],{"class":3861},[115,60738,60739],{"class":117,"line":2095},[115,60740,2552],{"class":125},[115,60742,60743],{"class":117,"line":2104},[115,60744,310],{"emptyLinePlaceholder":309},[115,60746,60747,60749,60751],{"class":117,"line":2113},[115,60748,11908],{"class":202},[115,60750,2380],{"class":121},[115,60752,11913],{"class":132},[115,60754,60755,60757,60759,60761,60763],{"class":117,"line":2122},[115,60756,11918],{"class":202},[115,60758,2380],{"class":121},[115,60760,11923],{"class":202},[115,60762,11926],{"class":121},[115,60764,11929],{"class":132},[115,60766,60767],{"class":117,"line":2131},[115,60768,310],{"emptyLinePlaceholder":309},[115,60770,60771,60773,60775],{"class":117,"line":2136},[115,60772,16659],{"class":202},[115,60774,2380],{"class":121},[115,60776,2166],{"class":125},[115,60778,60779,60781],{"class":117,"line":2142},[115,60780,16669],{"class":132},[115,60782,3374],{"class":125},[115,60784,60785,60787,60789,60791],{"class":117,"line":2273},[115,60786,16677],{"class":132},[115,60788,2513],{"class":125},[115,60790,16682],{"class":132},[115,60792,3354],{"class":125},[115,60794,60795],{"class":117,"line":2282},[115,60796,2233],{"class":125},[115,60798,60799],{"class":117,"line":2291},[115,60800,2323],{"class":125},[16,60802,60803],{},"Then test locally:",[106,60805,60806],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,60807,60808],{"__ignoreMap":111},[115,60809,60810,60812,60814,60816],{"class":117,"line":118},[115,60811,1114],{"class":262},[115,60813,1117],{"class":132},[115,60815,1838],{"class":132},[115,60817,1841],{"class":202},[16,60819,42059,60820,60822],{},[20,60821,13689],{}," during the build\u002Fdeploy process, not only as a one-off command after the app starts. Do not rely on the app’s local filesystem for persistent runtime-generated assets.",[16,60824,3515],{},[63,60826,60827,60833,60838],{},[66,60828,60829,60832],{},[20,60830,60831],{},"staticfiles\u002F"," is created during build or pre-release preparation",[66,60834,60835,60836],{},"Django admin CSS loads with ",[20,60837,2707],{},[66,60839,60840,60841],{},"no missing static file errors during ",[20,60842,13689],{},[16,60844,60845],{},"For user-uploaded media, do not rely on app-local storage for long-term production use. Use object storage for persistent media.",[52,60847,60849],{"id":60848},"define-the-app-start-command","Define the app start command",[16,60851,60852],{},"Railway needs a web start command. For a typical Django project:",[106,60854,60856],{"className":108,"code":60855,"language":110,"meta":111,"style":111},"gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT\n",[20,60857,60858],{"__ignoreMap":111},[115,60859,60860,60862,60865,60867,60870],{"class":117,"line":118},[115,60861,14954],{"class":262},[115,60863,60864],{"class":132}," myproject.wsgi:application",[115,60866,23605],{"class":202},[115,60868,60869],{"class":132}," 0.0.0.0:",[115,60871,60872],{"class":125},"$PORT\n",[16,60874,12628,60875,60878],{},[20,60876,60877],{},"myproject"," with your actual Django project module.",[16,60880,60881,60882,241],{},"You can also keep this in a ",[20,60883,60884],{},"Procfile",[106,60886,60888],{"className":108,"code":60887,"language":110,"meta":111,"style":111},"web: gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT\n",[20,60889,60890],{"__ignoreMap":111},[115,60891,60892,60895,60897,60899,60901,60903],{"class":117,"line":118},[115,60893,60894],{"class":262},"web:",[115,60896,2791],{"class":132},[115,60898,60864],{"class":132},[115,60900,23605],{"class":202},[115,60902,60869],{"class":132},[115,60904,60872],{"class":125},[16,60906,3515],{},[63,60908,60909,60912],{},[66,60910,60911],{},"the WSGI module path is correct",[66,60913,60914,60916,60917],{},[20,60915,14954],{}," is present in ",[20,60918,28328],{},[11,60920,60922],{"id":60921},"_2-create-the-railway-project-and-services","2) Create the Railway project and services",[52,60924,60926],{"id":60925},"create-a-new-railway-project","Create a new Railway project",[16,60928,60929],{},"In Railway:",[1173,60931,60932,60935,60938],{},[66,60933,60934],{},"create a new project",[66,60936,60937],{},"choose the GitHub deployment flow",[66,60939,60940],{},"connect the repository with your Django app",[16,60942,60943],{},"Railway should detect it as a Python app if your repo includes the expected Python project files.",[52,60945,60947],{"id":60946},"add-a-postgresql-service","Add a PostgreSQL service",[16,60949,60950,60951,60953],{},"Provision a PostgreSQL service in the Railway project. Railway commonly exposes the connection details through environment variables. Confirm that your app service receives a valid ",[20,60952,10873],{},". If not, copy the value manually into your app service variables.",[16,60955,60956],{},"Operational note: a hosted database is not a backup strategy by itself. Before risky schema changes, make sure you know how backups or restore points are handled for your environment.",[52,60958,60960],{"id":60959},"set-required-environment-variables","Set required environment variables",[16,60962,60963],{},"At minimum, configure:",[63,60965,60966,60970,60974,60979],{},[66,60967,60968],{},[20,60969,34901],{},[66,60971,60972],{},[20,60973,2707],{},[66,60975,60976],{},[20,60977,60978],{},"ALLOWED_HOSTS=your-app-domain.up.railway.app",[66,60980,60981],{},[20,60982,60983],{},"CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fyour-app-domain.up.railway.app",[16,60985,60986],{},"Add any other app-specific secrets:",[63,60988,60989,60991,60994,60996],{},[66,60990,32639],{},[66,60992,60993],{},"Redis URLs",[66,60995,32642],{},[66,60997,60998],{},"Sentry DSN",[16,61000,61001],{},"Do not commit these values into Git.",[11,61003,61005],{"id":61004},"_3-configure-railway-build-and-deploy-settings","3) Configure Railway build and deploy settings",[52,61007,61009],{"id":61008},"set-the-build-behavior","Set the build behavior",[16,61011,61012,61013,211],{},"Make sure Railway can install dependencies from ",[20,61014,28328],{},[16,61016,61017],{},"If you need a specific Python version, pin it using the Python version mechanism supported by your Railway build setup, and verify it in build logs.",[16,61019,61020],{},"The main requirement is consistency between local development and the Railway build environment.",[52,61022,61024],{"id":61023},"set-the-start-command","Set the start command",[16,61026,61027],{},"If Railway does not pick it up automatically, set the service start command to:",[106,61029,61030],{"className":108,"code":60855,"language":110,"meta":111,"style":111},[20,61031,61032],{"__ignoreMap":111},[115,61033,61034,61036,61038,61040,61042],{"class":117,"line":118},[115,61035,14954],{"class":262},[115,61037,60864],{"class":132},[115,61039,23605],{"class":202},[115,61041,60869],{"class":132},[115,61043,60872],{"class":125},[16,61045,61046],{},"You can tune worker count later, but start simple and verify stability first.",[52,61048,61050],{"id":61049},"make-static-collection-part-of-deploy","Make static collection part of deploy",[16,61052,61053],{},"Your deployment must include:",[106,61055,61056],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,61057,61058],{"__ignoreMap":111},[115,61059,61060,61062,61064,61066],{"class":117,"line":118},[115,61061,1114],{"class":262},[115,61063,1117],{"class":132},[115,61065,1838],{"class":132},[115,61067,1841],{"class":202},[16,61069,61070,61071,61073],{},"This should happen during build or as part of the deploy\u002Frelease process so the running service starts with static assets ready. Do not treat a manual post-deploy ",[20,61072,13689],{}," run as the normal production pattern.",[52,61075,61077],{"id":61076},"add-a-migration-step","Add a migration step",[16,61079,61080],{},"Run migrations as a controlled release step for the deployed version:",[106,61082,61083],{"className":108,"code":11313,"language":110,"meta":111,"style":111},[20,61084,61085],{"__ignoreMap":111},[115,61086,61087,61089,61091],{"class":117,"line":118},[115,61088,1114],{"class":262},[115,61090,1117],{"class":132},[115,61092,11324],{"class":132},[16,61094,61095],{},"Do not treat the deployment as healthy until migrations complete and database-backed pages have been verified.",[16,61097,61098],{},"Then verify migration state:",[106,61100,61101],{"className":108,"code":11330,"language":110,"meta":111,"style":111},[20,61102,61103],{"__ignoreMap":111},[115,61104,61105,61107,61109],{"class":117,"line":118},[115,61106,1114],{"class":262},[115,61108,1117],{"class":132},[115,61110,1129],{"class":132},[16,61112,61113],{},"If your deployment process supports a pre-deploy or release command, you can move migrations there later. Be careful with destructive migrations. Code rollback alone does not always fix a failed schema change.",[11,61115,61117],{"id":61116},"_4-deploy-django-on-railway-step-by-step","4) Deploy Django on Railway step by step",[52,61119,61121],{"id":61120},"push-the-code-and-trigger-the-first-deployment","Push the code and trigger the first deployment",[16,61123,61124],{},"Commit your changes and push to the branch connected to Railway:",[106,61126,61128],{"className":108,"code":61127,"language":110,"meta":111,"style":111},"git add .\ngit commit -m \"Prepare Django for Railway deployment\"\ngit push origin main\n",[20,61129,61130,61138,61150],{"__ignoreMap":111},[115,61131,61132,61134,61136],{"class":117,"line":118},[115,61133,13525],{"class":262},[115,61135,17622],{"class":132},[115,61137,17030],{"class":132},[115,61139,61140,61142,61145,61147],{"class":117,"line":136},[115,61141,13525],{"class":262},[115,61143,61144],{"class":132}," commit",[115,61146,12284],{"class":202},[115,61148,61149],{"class":132}," \"Prepare Django for Railway deployment\"\n",[115,61151,61152,61154,61156,61159],{"class":117,"line":149},[115,61153,13525],{"class":262},[115,61155,19112],{"class":132},[115,61157,61158],{"class":132}," origin",[115,61160,61161],{"class":132}," main\n",[16,61163,61164],{},"Watch the build logs for:",[63,61166,61167,61170,61175],{},[66,61168,61169],{},"dependency installation success",[66,61171,61172,61174],{},[20,61173,13689],{}," success",[66,61176,61177],{},"Gunicorn startup without import errors",[52,61179,28333],{"id":28332},[16,61181,61182],{},"Once the new version is built and ready for release, run:",[106,61184,61185],{"className":108,"code":11313,"language":110,"meta":111,"style":111},[20,61186,61187],{"__ignoreMap":111},[115,61188,61189,61191,61193],{"class":117,"line":118},[115,61190,1114],{"class":262},[115,61192,1117],{"class":132},[115,61194,11324],{"class":132},[16,61196,61197],{},"For higher-risk production changes, confirm you have a database recovery plan before applying schema changes.",[16,61199,3515],{},[63,61201,61202,61208],{},[66,61203,61204,61207],{},[20,61205,61206],{},"python manage.py showmigrations"," shows expected migrations as applied",[66,61209,61210],{},"database-backed pages load without errors",[52,61212,61214],{"id":61213},"verify-static-files","Verify static files",[16,61216,3515],{},[63,61218,61219,61222,61225],{},[66,61220,61221],{},"homepage CSS loads",[66,61223,61224],{},"admin CSS loads",[66,61226,61227],{},"browser dev tools show no obvious static 404s",[16,61229,61230],{},"If static assets are missing after deploy, fix the build\u002Fdeploy sequence rather than relying on ad hoc runtime filesystem changes.",[52,61232,61234],{"id":61233},"create-an-admin-user-if-needed","Create an admin user if needed",[16,61236,61237],{},"If this is a fresh environment:",[106,61239,61240],{"className":108,"code":15314,"language":110,"meta":111,"style":111},[20,61241,61242],{"__ignoreMap":111},[115,61243,61244,61246,61248],{"class":117,"line":118},[115,61245,1114],{"class":262},[115,61247,1117],{"class":132},[115,61249,15325],{"class":132},[16,61251,61252],{},"Avoid bootstrap scripts with hardcoded admin credentials. Use a one-time secure creation flow.",[11,61254,61256],{"id":61255},"_5-verify-the-railway-deployment","5) Verify the Railway deployment",[52,61258,61260],{"id":61259},"check-application-health","Check application health",[16,61262,61263],{},"Test:",[63,61265,61266,61269,61272,61275,61278],{},[66,61267,61268],{},"the homepage",[66,61270,61271],{},"a database-backed page",[66,61273,61274],{},"the Django admin login",[66,61276,61277],{},"static assets",[66,61279,61280],{},"Railway logs for startup or runtime errors",[16,61282,61283],{},"A simple command-level check is still useful before deployment:",[106,61285,61286],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,61287,61288],{"__ignoreMap":111},[115,61289,61290,61292,61294,61296],{"class":117,"line":118},[115,61291,1114],{"class":262},[115,61293,1117],{"class":132},[115,61295,1814],{"class":132},[115,61297,1817],{"class":202},[52,61299,61301],{"id":61300},"confirm-production-security-basics","Confirm production security basics",[16,61303,61304],{},"Verify that:",[63,61306,61307,61311,61316,61319,61324,61331],{},[66,61308,61309],{},[20,61310,2707],{},[66,61312,61313,61315],{},[20,61314,2713],{}," is not hardcoded",[66,61317,61318],{},"HTTPS works on the Railway domain or custom domain",[66,61320,61321,61323],{},[20,61322,2407],{}," is active for HTTPS-only deployments",[66,61325,61326,3146,61328,61330],{},[20,61327,2417],{},[20,61329,2426],{}," are enabled",[66,61332,61333],{},"HSTS is enabled only after the domain is fully HTTPS-only",[52,61335,61337],{"id":61336},"test-a-restart-and-config-persistence","Test a restart and config persistence",[16,61339,61340],{},"Restart or redeploy the service and confirm:",[63,61342,61343,61346,61349],{},[66,61344,61345],{},"environment variables are still present",[66,61347,61348],{},"the app reconnects to PostgreSQL",[66,61350,61351],{},"no startup-only assumptions were hiding config problems",[11,61353,61355],{"id":61354},"_6-add-a-custom-domain-and-production-hardening","6) Add a custom domain and production hardening",[52,61357,61359],{"id":61358},"attach-a-custom-domain-in-railway","Attach a custom domain in Railway",[16,61361,61362],{},"Add your domain in Railway and update your DNS records as instructed there. Wait for domain verification and HTTPS issuance.",[52,61364,61366],{"id":61365},"update-django-host-and-csrf-settings","Update Django host and CSRF settings",[16,61368,61369],{},"When the custom domain is active, update:",[106,61371,61374],{"className":61372,"code":61373,"language":247,"meta":111},[245],"ALLOWED_HOSTS=your-app-domain.up.railway.app,example.com,www.example.com\nCSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n",[20,61375,61373],{"__ignoreMap":111},[16,61377,61378,61380],{},[20,61379,2725],{}," must include the scheme.",[52,61382,61384],{"id":61383},"review-baseline-hardening","Review baseline hardening",[16,61386,61387],{},"At minimum, review:",[63,61389,61390,61393,61396,61399,61402],{},[66,61391,61392],{},"secure cookies",[66,61394,61395],{},"HSTS policy if the domain is fully HTTPS-only",[66,61397,61398],{},"logging level",[66,61400,61401],{},"error monitoring integration",[66,61403,61404],{},"trusted origins and proxy\u002FTLS settings",[11,61406,1321],{"id":1320},[16,61408,61409],{},"This setup works because it covers the parts Railway does not solve for you automatically: the Django app server, production settings, static file handling, proxy-aware security settings, and database configuration.",[16,61411,61412,61413,61415],{},"Gunicorn runs the Django app as a proper production web process. ",[20,61414,45465],{}," keeps database config environment-driven, which fits Railway well. Whitenoise is a practical default for smaller Django apps because it removes the need for a separate static file server. For larger apps or high static\u002Fmedia volume, object storage and a CDN are usually a better next step.",[16,61417,61418],{},"The important Railway-specific detail is that you should not depend on the service filesystem as durable application storage. That affects both media handling and any attempt to generate production assets after the app is already running.",[16,61420,61421],{},"Railway is a good choice when you want a simpler deployment path than managing your own VM, Nginx, process supervision, and TLS termination. If you need deeper network control, custom reverse proxy behavior, or stricter infrastructure ownership, a VPS or container platform may be a better fit.",[11,61423,1337],{"id":1336},[52,61425,61427],{"id":61426},"static-files-vs-media-files","Static files vs media files",[16,61429,61430],{},"Whitenoise is suitable for static assets collected at build or deploy time. It is not a full solution for user-uploaded media. Store media in persistent object storage.",[16,61432,61433],{},"On Railway, do not assume the service filesystem is durable across deploys or restarts.",[52,61435,61437],{"id":61436},"proxy-and-tls-headers","Proxy and TLS headers",[16,61439,61440],{},"Because TLS is terminated upstream, Django commonly needs:",[106,61442,61443],{"className":2369,"code":17575,"language":1114,"meta":111,"style":111},[20,61444,61445,61461],{"__ignoreMap":111},[115,61446,61447,61449,61451,61453,61455,61457,61459],{"class":117,"line":118},[115,61448,2377],{"class":202},[115,61450,2380],{"class":121},[115,61452,2383],{"class":125},[115,61454,2386],{"class":132},[115,61456,1153],{"class":125},[115,61458,2391],{"class":132},[115,61460,2394],{"class":125},[115,61462,61463,61465,61467],{"class":117,"line":136},[115,61464,12021],{"class":202},[115,61466,2380],{"class":121},[115,61468,2412],{"class":202},[16,61470,61471],{},"Without that, secure redirects, absolute URL generation, and host handling may be wrong.",[52,61473,61475],{"id":61474},"migration-risk","Migration risk",[16,61477,61478],{},"If a deploy fails after a destructive migration, rolling back code may not restore compatibility. Prefer backward-compatible migration sequences and know your database restore path before major releases.",[52,61480,61482],{"id":61481},"process-sizing-and-timeouts","Process sizing and timeouts",[16,61484,61485],{},"A single default Gunicorn command is enough to start, but you may need to tune workers and timeout behavior later based on memory use and request patterns.",[11,61487,61489],{"id":61488},"when-manual-railway-deployment-becomes-repetitive","When manual Railway deployment becomes repetitive",[16,61491,61492],{},"If you deploy multiple Django apps on Railway, the same work repeats: production settings scaffolding, env var validation, Gunicorn setup, static file config, and migration checks. Those steps are good candidates for reusable templates or deploy scripts. The first automation to add is usually preflight validation plus post-deploy smoke tests.",[11,61494,1386],{"id":1385},[16,61496,61497,61498,61500],{},"Before going deeper, review this ",[1395,61499,35524],{"href":3006}," to confirm your app is ready for any host.",[16,61502,61503,61504,211],{},"If you want to understand the application server and reverse proxy layer outside a managed platform, see ",[1395,61505,2986],{"href":2985},[16,61507,61508,61509,211],{},"If your project uses ASGI features such as websockets or long-lived connections, see ",[1395,61510,8039],{"href":8038},[16,61512,61513,61514,61516],{},"Before launch, use this ",[1395,61515,3000],{"href":2999}," to verify rollback readiness and operational checks.",[11,61518,1420],{"id":1419},[52,61520,61522],{"id":61521},"do-i-need-gunicorn-to-deploy-django-on-railway","Do I need Gunicorn to deploy Django on Railway?",[16,61524,61525],{},"Yes, for a standard Django production deployment, Gunicorn is the usual choice. Railway needs a web process that serves your Django app, and Gunicorn handles that reliably.",[52,61527,61529],{"id":61528},"how-do-i-configure-static-files-for-django-on-railway","How do I configure static files for Django on Railway?",[16,61531,49695,61532,61534,61535,61537,61538,61540],{},[20,61533,11918],{},", run ",[20,61536,13689],{},", and use Whitenoise if Django will serve static files itself. The important part is sequencing: ",[20,61539,13689],{}," should happen during build or deploy, not only as a one-off runtime command after the app is already serving traffic.",[52,61542,61544],{"id":61543},"how-do-i-run-migrations-during-a-railway-deployment","How do I run migrations during a Railway deployment?",[16,61546,42059,61547,61549],{},[20,61548,38156],{}," as a controlled release step for the deployed version. Do not treat the deploy as healthy until migrations complete and database-backed pages have been verified.",[52,61551,61553,61554,61556],{"id":61552},"why-is-my-django-app-returning-disallowedhost-on-railway","Why is my Django app returning ",[20,61555,46086],{}," on Railway?",[16,61558,61559,61560,61562],{},"Your Railway domain is probably missing from ",[20,61561,2719],{},". Add the Railway-generated domain and any custom domain to the environment variable, then redeploy.",[52,61564,61566],{"id":61565},"what-should-i-do-if-a-railway-deploy-succeeds-but-the-app-fails-after-migration","What should I do if a Railway deploy succeeds but the app fails after migration?",[16,61568,61569],{},"Check whether the issue is code, schema, or configuration. Rolling back app code may be enough for a safe migration, but not for destructive schema changes. For risky migrations, use backups or restore points and prefer backward-compatible release patterns.",[1485,61571,61572],{},"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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":61574},[61575,61576,61577,61578,61584,61589,61595,61601,61606,61611,61612,61618,61619,61620],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":60218,"depth":136,"text":60219,"children":61579},[61580,61581,61582,61583],{"id":60222,"depth":149,"text":60223},{"id":60294,"depth":149,"text":60295},{"id":60677,"depth":149,"text":60678},{"id":60848,"depth":149,"text":60849},{"id":60921,"depth":136,"text":60922,"children":61585},[61586,61587,61588],{"id":60925,"depth":149,"text":60926},{"id":60946,"depth":149,"text":60947},{"id":60959,"depth":149,"text":60960},{"id":61004,"depth":136,"text":61005,"children":61590},[61591,61592,61593,61594],{"id":61008,"depth":149,"text":61009},{"id":61023,"depth":149,"text":61024},{"id":61049,"depth":149,"text":61050},{"id":61076,"depth":149,"text":61077},{"id":61116,"depth":136,"text":61117,"children":61596},[61597,61598,61599,61600],{"id":61120,"depth":149,"text":61121},{"id":28332,"depth":149,"text":28333},{"id":61213,"depth":149,"text":61214},{"id":61233,"depth":149,"text":61234},{"id":61255,"depth":136,"text":61256,"children":61602},[61603,61604,61605],{"id":61259,"depth":149,"text":61260},{"id":61300,"depth":149,"text":61301},{"id":61336,"depth":149,"text":61337},{"id":61354,"depth":136,"text":61355,"children":61607},[61608,61609,61610],{"id":61358,"depth":149,"text":61359},{"id":61365,"depth":149,"text":61366},{"id":61383,"depth":149,"text":61384},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":61613},[61614,61615,61616,61617],{"id":61426,"depth":149,"text":61427},{"id":61436,"depth":149,"text":61437},{"id":61474,"depth":149,"text":61475},{"id":61481,"depth":149,"text":61482},{"id":61488,"depth":136,"text":61489},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":61621},[61622,61623,61624,61625,61627],{"id":61521,"depth":149,"text":61522},{"id":61528,"depth":149,"text":61529},{"id":61543,"depth":149,"text":61544},{"id":61552,"depth":149,"text":61626},"Why is my Django app returning DisallowedHost on Railway?",{"id":61565,"depth":149,"text":61566},"If you want to deploy Django on Railway, the main challenge is not just getting a container to start.",{},"\u002Fdeploy-django-on-railway","12",[11637,1403,2992],{"title":60129,"description":61628},[1557,61635,1558],"railway","deploy-django-on-railway",[1557,61635,1558],"Zgd5VaTPrXGRZvgy4nVYzu4wYNL9qCOvLQWDWnhyGUM",{"id":61640,"title":61641,"body":61642,"category":3088,"description":61648,"difficulty":3090,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":63431,"navigation":309,"path":63432,"priority":1549,"related":63433,"role":1553,"section":3098,"seo":63434,"stack":63435,"stem":63437,"tags":63438,"type":1561,"__hash__":63439},"articles\u002Fdeploy-django-on-render.md","How to Deploy Django on Render",{"type":8,"value":61643,"toc":63391},[61644,61646,61649,61652,61682,61685,61687,61690,61733,61736,61748,61750,61754,61756,61759,61788,61791,61795,61798,62057,62063,62066,62135,62138,62142,62145,62239,62242,62256,62259,62263,62266,62334,62336,62350,62354,62358,62361,62363,62374,62379,62381,62389,62395,62398,62403,62414,62417,62421,62425,62428,62430,62438,62442,62445,62471,62474,62490,62493,62501,62503,62513,62517,62520,62540,62543,62546,62614,62617,62629,62633,62638,62641,62653,62656,62659,62663,62670,62673,62718,62723,62774,62776,62790,62796,62799,62967,62970,62974,62977,62980,62993,62996,63008,63011,63023,63026,63028,63055,63059,63062,63065,63106,63112,63115,63160,63162,63174,63176,63179,63203,63209,63213,63219,63221,63281,63283,63290,63297,63305,63312,63314,63318,63321,63325,63328,63344,63348,63353,63357,63360,63376,63382,63388],[11,61645,14],{"id":13},[16,61647,61648],{},"It is easy to get a Django app running on Render, but it is also easy to ship an unsafe or incomplete production setup.",[16,61650,61651],{},"The common failures are predictable:",[63,61653,61654,61659,61664,61670,61673,61676,61679],{},[66,61655,61656,61658],{},[20,61657,7350],{}," left enabled",[66,61660,61661,61663],{},[20,61662,2713],{}," committed to Git",[66,61665,20107,61666,4493,61668],{},[20,61667,2719],{},[20,61669,2725],{},[66,61671,61672],{},"PostgreSQL not configured from environment variables",[66,61674,61675],{},"static files not collected or not served correctly",[66,61677,61678],{},"migrations forgotten during deployment",[66,61680,61681],{},"app code deployed successfully, but the database schema is still old",[16,61683,61684],{},"If you want to deploy Django on Render safely, you need a setup that covers the app server, database, static files, secrets, HTTPS-aware settings, and verification after release.",[11,61686,30],{"id":29},[16,61688,61689],{},"The shortest correct path to deploy Django on Render is:",[1173,61691,61692,61695,61705,61708,61711,61719,61722,61727,61730],{},[66,61693,61694],{},"Prepare your Django app for production",[66,61696,61697,61698,61700,61701,3146,61703],{},"Add ",[20,61699,14954],{},", a PostgreSQL driver, and optionally ",[20,61702,45465],{},[20,61704,43557],{},[66,61706,61707],{},"Create a PostgreSQL database on Render",[66,61709,61710],{},"Create a Python web service connected to your Git repository",[66,61712,61713,61714,1153,61716,61718],{},"Set environment variables such as ",[20,61715,2713],{},[20,61717,10873],{},", and host\u002FCSRF settings",[66,61720,61721],{},"Use Gunicorn as the start command",[66,61723,42059,61724,61726],{},[20,61725,13689],{}," during build",[66,61728,61729],{},"Apply migrations as part of the release process before treating the deploy as healthy",[66,61731,61732],{},"Verify homepage, admin, database writes, and static files",[16,61734,61735],{},"You can do this either:",[63,61737,61738,61741],{},[66,61739,61740],{},"manually in the Render dashboard",[66,61742,61743,61744,61747],{},"with a ",[20,61745,61746],{},"render.yaml"," blueprint for repeatable setup",[11,61749,43],{"id":42},[11,61751,61753],{"id":61752},"_1-prepare-your-django-app-for-render","1. Prepare your Django app for Render",[52,61755,60223],{"id":60222},[16,61757,61758],{},"Install the common packages used for a Django deployment on Render:",[106,61760,61762],{"className":108,"code":61761,"language":110,"meta":111,"style":111},"pip install gunicorn dj-database-url whitenoise psycopg[binary]\npip freeze > requirements.txt\n",[20,61763,61764,61778],{"__ignoreMap":111},[115,61765,61766,61768,61770,61772,61774,61776],{"class":117,"line":118},[115,61767,8618],{"class":262},[115,61769,6600],{"class":132},[115,61771,2791],{"class":132},[115,61773,60244],{"class":132},[115,61775,16313],{"class":132},[115,61777,60247],{"class":132},[115,61779,61780,61782,61784,61786],{"class":117,"line":136},[115,61781,8618],{"class":262},[115,61783,16323],{"class":132},[115,61785,604],{"class":121},[115,61787,12353],{"class":132},[16,61789,61790],{},"If your project already uses a lockfile workflow, keep using that. The important part is that Render can install the exact dependencies during build.",[52,61792,61794],{"id":61793},"configure-production-settings","Configure production settings",[16,61796,61797],{},"A minimal production-safe settings pattern looks like this:",[106,61799,61801],{"className":2369,"code":61800,"language":1114,"meta":111,"style":111},"import os\nfrom pathlib import Path\nimport dj_database_url\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = os.environ.get(\"DEBUG\", \"False\").lower() == \"true\"\n\nSECRET_KEY = os.environ[\"SECRET_KEY\"]\n\nALLOWED_HOSTS = [\n    \"your-service.onrender.com\",\n    \"yourdomain.com\",\n]\n\nCSRF_TRUSTED_ORIGINS = [\n    \"https:\u002F\u002Fyour-service.onrender.com\",\n    \"https:\u002F\u002Fyourdomain.com\",\n]\n\nDATABASES = {\n    \"default\": dj_database_url.parse(\n        os.environ[\"DATABASE_URL\"],\n        conn_max_age=600,\n    )\n}\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_SSL_REDIRECT = True\n\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n",[20,61802,61803,61809,61819,61825,61829,61841,61845,61865,61869,61881,61885,61893,61900,61907,61911,61915,61923,61930,61937,61941,61945,61953,61959,61967,61977,61981,61985,61989,62005,62013,62021,62029,62033,62041,62049],{"__ignoreMap":111},[115,61804,61805,61807],{"class":117,"line":118},[115,61806,5613],{"class":121},[115,61808,5616],{"class":125},[115,61810,61811,61813,61815,61817],{"class":117,"line":136},[115,61812,5621],{"class":121},[115,61814,16353],{"class":125},[115,61816,5613],{"class":121},[115,61818,16358],{"class":125},[115,61820,61821,61823],{"class":117,"line":149},[115,61822,5613],{"class":121},[115,61824,16365],{"class":125},[115,61826,61827],{"class":117,"line":162},[115,61828,310],{"emptyLinePlaceholder":309},[115,61830,61831,61833,61835,61837,61839],{"class":117,"line":175},[115,61832,16374],{"class":202},[115,61834,2380],{"class":121},[115,61836,16379],{"class":125},[115,61838,16382],{"class":202},[115,61840,34339],{"class":125},[115,61842,61843],{"class":117,"line":350},[115,61844,310],{"emptyLinePlaceholder":309},[115,61846,61847,61849,61851,61853,61855,61857,61859,61861,61863],{"class":117,"line":365},[115,61848,7350],{"class":202},[115,61850,2380],{"class":121},[115,61852,8884],{"class":125},[115,61854,60356],{"class":132},[115,61856,1153],{"class":125},[115,61858,25169],{"class":132},[115,61860,25172],{"class":125},[115,61862,25175],{"class":121},[115,61864,25178],{"class":132},[115,61866,61867],{"class":117,"line":380},[115,61868,310],{"emptyLinePlaceholder":309},[115,61870,61871,61873,61875,61877,61879],{"class":117,"line":487},[115,61872,2713],{"class":202},[115,61874,2380],{"class":121},[115,61876,8861],{"class":125},[115,61878,16412],{"class":132},[115,61880,2552],{"class":125},[115,61882,61883],{"class":117,"line":2095},[115,61884,310],{"emptyLinePlaceholder":309},[115,61886,61887,61889,61891],{"class":117,"line":2104},[115,61888,2719],{"class":202},[115,61890,2380],{"class":121},[115,61892,3540],{"class":125},[115,61894,61895,61898],{"class":117,"line":2113},[115,61896,61897],{"class":132},"    \"your-service.onrender.com\"",[115,61899,3354],{"class":125},[115,61901,61902,61905],{"class":117,"line":2122},[115,61903,61904],{"class":132},"    \"yourdomain.com\"",[115,61906,3354],{"class":125},[115,61908,61909],{"class":117,"line":2131},[115,61910,2552],{"class":125},[115,61912,61913],{"class":117,"line":2136},[115,61914,310],{"emptyLinePlaceholder":309},[115,61916,61917,61919,61921],{"class":117,"line":2142},[115,61918,2725],{"class":202},[115,61920,2380],{"class":121},[115,61922,3540],{"class":125},[115,61924,61925,61928],{"class":117,"line":2273},[115,61926,61927],{"class":132},"    \"https:\u002F\u002Fyour-service.onrender.com\"",[115,61929,3354],{"class":125},[115,61931,61932,61935],{"class":117,"line":2282},[115,61933,61934],{"class":132},"    \"https:\u002F\u002Fyourdomain.com\"",[115,61936,3354],{"class":125},[115,61938,61939],{"class":117,"line":2291},[115,61940,2552],{"class":125},[115,61942,61943],{"class":117,"line":2299},[115,61944,310],{"emptyLinePlaceholder":309},[115,61946,61947,61949,61951],{"class":117,"line":2307},[115,61948,10632],{"class":202},[115,61950,2380],{"class":121},[115,61952,2166],{"class":125},[115,61954,61955,61957],{"class":117,"line":2315},[115,61956,10664],{"class":132},[115,61958,60499],{"class":125},[115,61960,61961,61963,61965],{"class":117,"line":2320},[115,61962,60504],{"class":125},[115,61964,16727],{"class":132},[115,61966,3430],{"class":125},[115,61968,61969,61971,61973,61975],{"class":117,"line":7083},[115,61970,16735],{"class":5680},[115,61972,129],{"class":121},[115,61974,16740],{"class":202},[115,61976,3354],{"class":125},[115,61978,61979],{"class":117,"line":7090},[115,61980,16748],{"class":125},[115,61982,61983],{"class":117,"line":7097},[115,61984,2323],{"class":125},[115,61986,61987],{"class":117,"line":7108},[115,61988,310],{"emptyLinePlaceholder":309},[115,61990,61991,61993,61995,61997,61999,62001,62003],{"class":117,"line":7113},[115,61992,2377],{"class":202},[115,61994,2380],{"class":121},[115,61996,2383],{"class":125},[115,61998,2386],{"class":132},[115,62000,1153],{"class":125},[115,62002,2391],{"class":132},[115,62004,2394],{"class":125},[115,62006,62007,62009,62011],{"class":117,"line":16535},[115,62008,2417],{"class":202},[115,62010,2380],{"class":121},[115,62012,2412],{"class":202},[115,62014,62015,62017,62019],{"class":117,"line":16544},[115,62016,2426],{"class":202},[115,62018,2380],{"class":121},[115,62020,2412],{"class":202},[115,62022,62023,62025,62027],{"class":117,"line":16549},[115,62024,2407],{"class":202},[115,62026,2380],{"class":121},[115,62028,2412],{"class":202},[115,62030,62031],{"class":117,"line":16555},[115,62032,310],{"emptyLinePlaceholder":309},[115,62034,62035,62037,62039],{"class":117,"line":16564},[115,62036,7440],{"class":202},[115,62038,2380],{"class":121},[115,62040,11991],{"class":202},[115,62042,62043,62045,62047],{"class":117,"line":16573},[115,62044,7464],{"class":202},[115,62046,2380],{"class":121},[115,62048,2412],{"class":202},[115,62050,62051,62053,62055],{"class":117,"line":16582},[115,62052,12004],{"class":202},[115,62054,2380],{"class":121},[115,62056,7355],{"class":202},[16,62058,62059,62060,62062],{},"If you prefer ",[20,62061,2719],{}," from environment variables, that is fine too. Just make sure the Render hostname and any custom domain are included.",[16,62064,62065],{},"A safe parsing pattern is:",[106,62067,62069],{"className":2369,"code":62068,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [h for h in os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\") if h]\nCSRF_TRUSTED_ORIGINS = [o for o in os.environ.get(\"CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if o]\n",[20,62070,62071,62103],{"__ignoreMap":111},[115,62072,62073,62075,62077,62079,62081,62083,62085,62087,62089,62091,62093,62095,62097,62099,62101],{"class":117,"line":118},[115,62074,2719],{"class":202},[115,62076,2380],{"class":121},[115,62078,25191],{"class":125},[115,62080,18256],{"class":121},[115,62082,18259],{"class":125},[115,62084,18262],{"class":121},[115,62086,8884],{"class":125},[115,62088,18267],{"class":132},[115,62090,1153],{"class":125},[115,62092,18272],{"class":132},[115,62094,18275],{"class":125},[115,62096,18278],{"class":132},[115,62098,18281],{"class":125},[115,62100,10833],{"class":121},[115,62102,25217],{"class":125},[115,62104,62105,62107,62109,62111,62113,62115,62117,62119,62121,62123,62125,62127,62129,62131,62133],{"class":117,"line":136},[115,62106,2725],{"class":202},[115,62108,2380],{"class":121},[115,62110,25226],{"class":125},[115,62112,18256],{"class":121},[115,62114,18300],{"class":125},[115,62116,18262],{"class":121},[115,62118,8884],{"class":125},[115,62120,18307],{"class":132},[115,62122,1153],{"class":125},[115,62124,18272],{"class":132},[115,62126,18275],{"class":125},[115,62128,18278],{"class":132},[115,62130,18281],{"class":125},[115,62132,10833],{"class":121},[115,62134,25252],{"class":125},[16,62136,62137],{},"If you enable HSTS, do it only after HTTPS is confirmed to work correctly on every intended domain.",[52,62139,62141],{"id":62140},"make-static-files-work-on-render","Make static files work on Render",[16,62143,62144],{},"If you want Django to serve static files directly, WhiteNoise is the simplest starting point.",[106,62146,62148],{"className":2369,"code":62147,"language":1114,"meta":111,"style":111},"MIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # other middleware...\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nSTORAGES = {\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    },\n}\n",[20,62149,62150,62158,62164,62170,62175,62179,62183,62191,62203,62207,62215,62221,62231,62235],{"__ignoreMap":111},[115,62151,62152,62154,62156],{"class":117,"line":118},[115,62153,16617],{"class":202},[115,62155,2380],{"class":121},[115,62157,3540],{"class":125},[115,62159,62160,62162],{"class":117,"line":136},[115,62161,16627],{"class":132},[115,62163,3354],{"class":125},[115,62165,62166,62168],{"class":117,"line":149},[115,62167,16635],{"class":132},[115,62169,3354],{"class":125},[115,62171,62172],{"class":117,"line":162},[115,62173,62174],{"class":3861},"    # other middleware...\n",[115,62176,62177],{"class":117,"line":175},[115,62178,2552],{"class":125},[115,62180,62181],{"class":117,"line":350},[115,62182,310],{"emptyLinePlaceholder":309},[115,62184,62185,62187,62189],{"class":117,"line":365},[115,62186,11908],{"class":202},[115,62188,2380],{"class":121},[115,62190,11913],{"class":132},[115,62192,62193,62195,62197,62199,62201],{"class":117,"line":380},[115,62194,11918],{"class":202},[115,62196,2380],{"class":121},[115,62198,11923],{"class":202},[115,62200,11926],{"class":121},[115,62202,11929],{"class":132},[115,62204,62205],{"class":117,"line":487},[115,62206,310],{"emptyLinePlaceholder":309},[115,62208,62209,62211,62213],{"class":117,"line":2095},[115,62210,16659],{"class":202},[115,62212,2380],{"class":121},[115,62214,2166],{"class":125},[115,62216,62217,62219],{"class":117,"line":2104},[115,62218,16669],{"class":132},[115,62220,3374],{"class":125},[115,62222,62223,62225,62227,62229],{"class":117,"line":2113},[115,62224,16677],{"class":132},[115,62226,2513],{"class":125},[115,62228,16682],{"class":132},[115,62230,3354],{"class":125},[115,62232,62233],{"class":117,"line":2122},[115,62234,3403],{"class":125},[115,62236,62237],{"class":117,"line":2131},[115,62238,2323],{"class":125},[16,62240,62241],{},"Then collect static files:",[106,62243,62244],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,62245,62246],{"__ignoreMap":111},[115,62247,62248,62250,62252,62254],{"class":117,"line":118},[115,62249,1114],{"class":262},[115,62251,1117],{"class":132},[115,62253,1838],{"class":132},[115,62255,1841],{"class":202},[16,62257,62258],{},"For many apps, WhiteNoise is enough. If your static or media workload grows, object storage is usually the better next step, especially for user-uploaded files.",[52,62260,62262],{"id":62261},"verify-locally-in-production-mode","Verify locally in production mode",[16,62264,62265],{},"Before deploying, test with production-like settings locally:",[106,62267,62269],{"className":108,"code":62268,"language":110,"meta":111,"style":111},"export DEBUG=False\nexport SECRET_KEY='replace-me'\nexport DATABASE_URL='postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb'\npython manage.py collectstatic --noinput\npython manage.py migrate\ngunicorn myproject.wsgi:application --bind 0.0.0.0:8000\n",[20,62270,62271,62282,62294,62305,62315,62323],{"__ignoreMap":111},[115,62272,62273,62275,62278,62280],{"class":117,"line":118},[115,62274,122],{"class":121},[115,62276,62277],{"class":125}," DEBUG",[115,62279,129],{"class":121},[115,62281,45402],{"class":125},[115,62283,62284,62286,62289,62291],{"class":117,"line":136},[115,62285,122],{"class":121},[115,62287,62288],{"class":125}," SECRET_KEY",[115,62290,129],{"class":121},[115,62292,62293],{"class":132},"'replace-me'\n",[115,62295,62296,62298,62300,62302],{"class":117,"line":149},[115,62297,122],{"class":121},[115,62299,10511],{"class":125},[115,62301,129],{"class":121},[115,62303,62304],{"class":132},"'postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb'\n",[115,62306,62307,62309,62311,62313],{"class":117,"line":162},[115,62308,1114],{"class":262},[115,62310,1117],{"class":132},[115,62312,1838],{"class":132},[115,62314,1841],{"class":202},[115,62316,62317,62319,62321],{"class":117,"line":175},[115,62318,1114],{"class":262},[115,62320,1117],{"class":132},[115,62322,11324],{"class":132},[115,62324,62325,62327,62329,62331],{"class":117,"line":350},[115,62326,14954],{"class":262},[115,62328,60864],{"class":132},[115,62330,23605],{"class":202},[115,62332,62333],{"class":132}," 0.0.0.0:8000\n",[16,62335,5438],{},[63,62337,62338,62341,62344,62347],{},[66,62339,62340],{},"app boots without Django debug mode",[66,62342,62343],{},"static files resolve",[66,62345,62346],{},"database connection works",[66,62348,62349],{},"no settings rely on local-only assumptions",[11,62351,62353],{"id":62352},"_2-create-the-supporting-services-on-render","2. Create the supporting services on Render",[52,62355,62357],{"id":62356},"create-a-postgresql-database","Create a PostgreSQL database",[16,62359,62360],{},"In Render, create a managed PostgreSQL database first.",[16,62362,17076],{},[63,62364,62365,62368,62371],{},[66,62366,62367],{},"the same region as the web service",[66,62369,62370],{},"an appropriate plan for your environment",[66,62372,62373],{},"backup expectations that match your recovery needs",[16,62375,62376,62377,211],{},"Once created, Render provides a connection string you can use as ",[20,62378,10873],{},[16,62380,3515],{},[63,62382,62383,62386],{},[66,62384,62385],{},"confirm the database status is healthy",[66,62387,62388],{},"confirm you can reference its connection string from your app service",[52,62390,62392,62393],{"id":62391},"decide-between-dashboard-setup-and-renderyaml","Decide between dashboard setup and ",[20,62394,61746],{},[16,62396,62397],{},"Manual dashboard setup is fine for a first deployment or a single environment.",[16,62399,55,62400,62402],{},[20,62401,61746],{}," when you want:",[63,62404,62405,62408,62411],{},[66,62406,62407],{},"repeatable environment creation",[66,62409,62410],{},"lower config drift",[66,62412,62413],{},"service definitions stored in Git",[16,62415,62416],{},"For teams or multiple environments, blueprint-based deployment is usually easier to maintain.",[11,62418,62420],{"id":62419},"_3-deploy-django-on-render-from-the-dashboard","3. Deploy Django on Render from the dashboard",[52,62422,62424],{"id":62423},"connect-the-repository","Connect the repository",[16,62426,62427],{},"Create a new Web Service in Render and connect your GitHub or GitLab repository.",[16,62429,17076],{},[63,62431,62432,62435],{},[66,62433,62434],{},"the correct branch",[66,62436,62437],{},"Python as the environment",[52,62439,62441],{"id":62440},"configure-the-web-service","Configure the web service",[16,62443,62444],{},"Use a build command that installs dependencies and collects static files:",[106,62446,62448],{"className":108,"code":62447,"language":110,"meta":111,"style":111},"pip install -r requirements.txt && python manage.py collectstatic --noinput\n",[20,62449,62450],{"__ignoreMap":111},[115,62451,62452,62454,62456,62458,62461,62463,62465,62467,62469],{"class":117,"line":118},[115,62453,8618],{"class":262},[115,62455,6600],{"class":132},[115,62457,12350],{"class":202},[115,62459,62460],{"class":132}," requirements.txt",[115,62462,3912],{"class":125},[115,62464,1114],{"class":262},[115,62466,1117],{"class":132},[115,62468,1838],{"class":132},[115,62470,1841],{"class":202},[16,62472,62473],{},"Use a start command that runs Gunicorn on Render’s assigned port:",[106,62475,62476],{"className":108,"code":60855,"language":110,"meta":111,"style":111},[20,62477,62478],{"__ignoreMap":111},[115,62479,62480,62482,62484,62486,62488],{"class":117,"line":118},[115,62481,14954],{"class":262},[115,62483,60864],{"class":132},[115,62485,23605],{"class":202},[115,62487,60869],{"class":132},[115,62489,60872],{"class":125},[16,62491,62492],{},"Also make sure:",[63,62494,62495,62498],{},[66,62496,62497],{},"the web service region matches the database region",[66,62499,62500],{},"the instance type is appropriate for your app size",[16,62502,3515],{},[63,62504,62505,62510],{},[66,62506,62507,62508],{},"build logs should show dependency installation and successful ",[20,62509,13689],{},[66,62511,62512],{},"startup logs should show Gunicorn booting cleanly",[52,62514,62516],{"id":62515},"set-environment-variables","Set environment variables",[16,62518,62519],{},"In the Render dashboard, add at least:",[63,62521,62522,62526,62530,62534,62537],{},[66,62523,62524],{},[20,62525,2713],{},[66,62527,62528],{},[20,62529,2707],{},[66,62531,62532],{},[20,62533,10873],{},[66,62535,62536],{},"any app-specific secrets",[66,62538,62539],{},"optionally host lists if you load them from environment",[16,62541,62542],{},"Do not hardcode secrets in the repository.",[16,62544,62545],{},"If you manage host settings via environment variables, a common pattern is:",[106,62547,62548],{"className":2369,"code":62068,"language":1114,"meta":111,"style":111},[20,62549,62550,62582],{"__ignoreMap":111},[115,62551,62552,62554,62556,62558,62560,62562,62564,62566,62568,62570,62572,62574,62576,62578,62580],{"class":117,"line":118},[115,62553,2719],{"class":202},[115,62555,2380],{"class":121},[115,62557,25191],{"class":125},[115,62559,18256],{"class":121},[115,62561,18259],{"class":125},[115,62563,18262],{"class":121},[115,62565,8884],{"class":125},[115,62567,18267],{"class":132},[115,62569,1153],{"class":125},[115,62571,18272],{"class":132},[115,62573,18275],{"class":125},[115,62575,18278],{"class":132},[115,62577,18281],{"class":125},[115,62579,10833],{"class":121},[115,62581,25217],{"class":125},[115,62583,62584,62586,62588,62590,62592,62594,62596,62598,62600,62602,62604,62606,62608,62610,62612],{"class":117,"line":136},[115,62585,2725],{"class":202},[115,62587,2380],{"class":121},[115,62589,25226],{"class":125},[115,62591,18256],{"class":121},[115,62593,18300],{"class":125},[115,62595,18262],{"class":121},[115,62597,8884],{"class":125},[115,62599,18307],{"class":132},[115,62601,1153],{"class":125},[115,62603,18272],{"class":132},[115,62605,18275],{"class":125},[115,62607,18278],{"class":132},[115,62609,18281],{"class":125},[115,62611,10833],{"class":121},[115,62613,25252],{"class":125},[16,62615,62616],{},"Then set values like:",[63,62618,62619,62624],{},[66,62620,62621],{},[20,62622,62623],{},"ALLOWED_HOSTS=your-service.onrender.com,yourdomain.com",[66,62625,62626],{},[20,62627,62628],{},"CSRF_TRUSTED_ORIGINS=https:\u002F\u002Fyour-service.onrender.com,https:\u002F\u002Fyourdomain.com",[52,62630,62632],{"id":62631},"configure-migrations-and-static-file-collection","Configure migrations and static file collection",[16,62634,62635,62637],{},[20,62636,13689],{}," fits naturally in the build step. Migrations need more care.",[16,62639,62640],{},"Do not let a new release depend on manual post-deploy migrations. Run:",[106,62642,62643],{"className":108,"code":11313,"language":110,"meta":111,"style":111},[20,62644,62645],{"__ignoreMap":111},[115,62646,62647,62649,62651],{"class":117,"line":118},[115,62648,1114],{"class":262},[115,62650,1117],{"class":132},[115,62652,11324],{"class":132},[16,62654,62655],{},"as part of a controlled release step before treating the deployment as healthy. If you cannot automate that safely yet, restrict production use until migration completion is verified, and prefer backward-compatible migrations.",[16,62657,62658],{},"Rollback note: application code is easier to roll back than database schema. Prefer additive, backward-compatible migrations where possible.",[52,62660,62662],{"id":62661},"add-a-health-check-path","Add a health check path",[16,62664,62665,62666,62669],{},"Configure a simple health endpoint that does not require login, for example ",[20,62667,62668],{},"\u002Fhealthz\u002F",", and point Render health checks to it if your service setup uses custom health check paths.",[16,62671,62672],{},"A minimal Django view can be as simple as:",[106,62674,62676],{"className":2369,"code":62675,"language":1114,"meta":111,"style":111},"from django.http import HttpResponse\n\ndef healthz(request):\n    return HttpResponse(\"ok\", content_type=\"text\u002Fplain\")\n",[20,62677,62678,62688,62692,62700],{"__ignoreMap":111},[115,62679,62680,62682,62684,62686],{"class":117,"line":118},[115,62681,5621],{"class":121},[115,62683,17240],{"class":125},[115,62685,5613],{"class":121},[115,62687,17245],{"class":125},[115,62689,62690],{"class":117,"line":136},[115,62691,310],{"emptyLinePlaceholder":309},[115,62693,62694,62696,62698],{"class":117,"line":149},[115,62695,8808],{"class":121},[115,62697,17268],{"class":262},[115,62699,17271],{"class":125},[115,62701,62702,62704,62706,62708,62710,62712,62714,62716],{"class":117,"line":162},[115,62703,3822],{"class":121},[115,62705,17278],{"class":125},[115,62707,17281],{"class":132},[115,62709,1153],{"class":125},[115,62711,17286],{"class":5680},[115,62713,129],{"class":121},[115,62715,17291],{"class":132},[115,62717,2394],{"class":125},[16,62719,5696,62720,241],{},[20,62721,62722],{},"urls.py",[106,62724,62726],{"className":2369,"code":62725,"language":1114,"meta":111,"style":111},"from django.urls import path\nfrom .views import healthz\n\nurlpatterns = [\n    path(\"healthz\u002F\", healthz),\n]\n",[20,62727,62728,62738,62749,62753,62761,62770],{"__ignoreMap":111},[115,62729,62730,62732,62734,62736],{"class":117,"line":118},[115,62731,5621],{"class":121},[115,62733,17252],{"class":125},[115,62735,5613],{"class":121},[115,62737,17257],{"class":125},[115,62739,62740,62742,62744,62746],{"class":117,"line":136},[115,62741,5621],{"class":121},[115,62743,53674],{"class":125},[115,62745,5613],{"class":121},[115,62747,62748],{"class":125}," healthz\n",[115,62750,62751],{"class":117,"line":149},[115,62752,310],{"emptyLinePlaceholder":309},[115,62754,62755,62757,62759],{"class":117,"line":162},[115,62756,17302],{"class":125},[115,62758,129],{"class":121},[115,62760,3540],{"class":125},[115,62762,62763,62765,62768],{"class":117,"line":175},[115,62764,17311],{"class":125},[115,62766,62767],{"class":132},"\"healthz\u002F\"",[115,62769,17317],{"class":125},[115,62771,62772],{"class":117,"line":350},[115,62773,2552],{"class":125},[16,62775,3515],{},[63,62777,62778,62784,62787],{},[66,62779,62780,20014,62782],{},[20,62781,62668],{},[20,62783,17741],{},[66,62785,62786],{},"it does not depend on session state or admin login",[66,62788,62789],{},"it still works after a fresh deploy",[11,62791,62793,62794],{"id":62792},"_4-deploy-django-on-render-with-renderyaml","4. Deploy Django on Render with ",[20,62795,61746],{},[16,62797,62798],{},"A simple blueprint can define both the web service and database:",[106,62800,62802],{"className":2485,"code":62801,"language":2487,"meta":111,"style":111},"services:\n  - type: web\n    name: my-django-app\n    env: python\n    buildCommand: pip install -r requirements.txt && python manage.py collectstatic --noinput\n    startCommand: gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT\n    envVars:\n      - key: SECRET_KEY\n        generateValue: true\n      - key: DEBUG\n        value: \"False\"\n      - key: DATABASE_URL\n        fromDatabase:\n          name: my-django-db\n          property: connectionString\n\ndatabases:\n  - name: my-django-db\n",[20,62803,62804,62810,62821,62831,62841,62850,62859,62866,62878,62887,62898,62908,62919,62926,62936,62946,62950,62957],{"__ignoreMap":111},[115,62805,62806,62808],{"class":117,"line":118},[115,62807,2495],{"class":2494},[115,62809,2498],{"class":125},[115,62811,62812,62814,62817,62819],{"class":117,"line":136},[115,62813,20762],{"class":125},[115,62815,62816],{"class":2494},"type",[115,62818,2513],{"class":125},[115,62820,20795],{"class":132},[115,62822,62823,62826,62828],{"class":117,"line":149},[115,62824,62825],{"class":2494},"    name",[115,62827,2513],{"class":125},[115,62829,62830],{"class":132},"my-django-app\n",[115,62832,62833,62836,62838],{"class":117,"line":162},[115,62834,62835],{"class":2494},"    env",[115,62837,2513],{"class":125},[115,62839,62840],{"class":132},"python\n",[115,62842,62843,62846,62848],{"class":117,"line":175},[115,62844,62845],{"class":2494},"    buildCommand",[115,62847,2513],{"class":125},[115,62849,62447],{"class":132},[115,62851,62852,62855,62857],{"class":117,"line":350},[115,62853,62854],{"class":2494},"    startCommand",[115,62856,2513],{"class":125},[115,62858,60855],{"class":132},[115,62860,62861,62864],{"class":117,"line":365},[115,62862,62863],{"class":2494},"    envVars",[115,62865,2498],{"class":125},[115,62867,62868,62870,62873,62875],{"class":117,"line":380},[115,62869,5976],{"class":125},[115,62871,62872],{"class":2494},"key",[115,62874,2513],{"class":125},[115,62876,62877],{"class":132},"SECRET_KEY\n",[115,62879,62880,62883,62885],{"class":117,"line":487},[115,62881,62882],{"class":2494},"        generateValue",[115,62884,2513],{"class":125},[115,62886,20805],{"class":202},[115,62888,62889,62891,62893,62895],{"class":117,"line":2095},[115,62890,5976],{"class":125},[115,62892,62872],{"class":2494},[115,62894,2513],{"class":125},[115,62896,62897],{"class":132},"DEBUG\n",[115,62899,62900,62903,62905],{"class":117,"line":2104},[115,62901,62902],{"class":2494},"        value",[115,62904,2513],{"class":125},[115,62906,62907],{"class":132},"\"False\"\n",[115,62909,62910,62912,62914,62916],{"class":117,"line":2113},[115,62911,5976],{"class":125},[115,62913,62872],{"class":2494},[115,62915,2513],{"class":125},[115,62917,62918],{"class":132},"DATABASE_URL\n",[115,62920,62921,62924],{"class":117,"line":2122},[115,62922,62923],{"class":2494},"        fromDatabase",[115,62925,2498],{"class":125},[115,62927,62928,62931,62933],{"class":117,"line":2131},[115,62929,62930],{"class":2494},"          name",[115,62932,2513],{"class":125},[115,62934,62935],{"class":132},"my-django-db\n",[115,62937,62938,62941,62943],{"class":117,"line":2136},[115,62939,62940],{"class":2494},"          property",[115,62942,2513],{"class":125},[115,62944,62945],{"class":132},"connectionString\n",[115,62947,62948],{"class":117,"line":2142},[115,62949,310],{"emptyLinePlaceholder":309},[115,62951,62952,62955],{"class":117,"line":2273},[115,62953,62954],{"class":2494},"databases",[115,62956,2498],{"class":125},[115,62958,62959,62961,62963,62965],{"class":117,"line":2282},[115,62960,20762],{"class":125},[115,62962,20820],{"class":2494},[115,62964,2513],{"class":125},[115,62966,62935],{"class":132},[16,62968,62969],{},"This is useful because the deployment definition lives with the application code. That reduces manual drift across environments.",[11,62971,62973],{"id":62972},"_5-run-the-first-production-deployment","5. Run the first production deployment",[16,62975,62976],{},"Trigger the deploy and watch the logs.",[16,62978,62979],{},"You want to see:",[63,62981,62982,62985,62990],{},[66,62983,62984],{},"dependencies installed successfully",[66,62986,62987,62989],{},[20,62988,13689],{}," complete without errors",[66,62991,62992],{},"Gunicorn starting without import or settings failures",[16,62994,62995],{},"Ensure migrations are applied as part of the release process:",[106,62997,62998],{"className":108,"code":11313,"language":110,"meta":111,"style":111},[20,62999,63000],{"__ignoreMap":111},[115,63001,63002,63004,63006],{"class":117,"line":118},[115,63003,1114],{"class":262},[115,63005,1117],{"class":132},[115,63007,11324],{"class":132},[16,63009,63010],{},"If you need Django admin access, create a superuser:",[106,63012,63013],{"className":108,"code":15314,"language":110,"meta":111,"style":111},[20,63014,63015],{"__ignoreMap":111},[115,63016,63017,63019,63021],{"class":117,"line":118},[115,63018,1114],{"class":262},[115,63020,1117],{"class":132},[115,63022,15325],{"class":132},[16,63024,63025],{},"Use this as a controlled one-time setup step. Over time, many teams replace interactive production admin setup with scripted or managed workflows.",[16,63027,3515],{},[63,63029,63030,63034,63039,63041,63044,63049],{},[66,63031,26740,63032],{},[20,63033,17741],{},[66,63035,63036,63038],{},[20,63037,53499],{}," loads",[66,63040,33784],{},[66,63042,63043],{},"a database-backed page can read and write data",[66,63045,63046,63047],{},"static assets load without ",[20,63048,19897],{},[66,63050,63051,20014,63053],{},[20,63052,62668],{},[20,63054,17741],{},[11,63056,63058],{"id":63057},"_6-configure-a-custom-domain-and-https","6. Configure a custom domain and HTTPS",[16,63060,63061],{},"Attach the custom domain in Render and update your DNS records as instructed in the Render dashboard. DNS changes may take time to propagate.",[16,63063,63064],{},"After that, update Django:",[106,63066,63068],{"className":2369,"code":63067,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\"your-service.onrender.com\", \"yourdomain.com\"]\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fyour-service.onrender.com\", \"https:\u002F\u002Fyourdomain.com\"]\n",[20,63069,63070,63088],{"__ignoreMap":111},[115,63071,63072,63074,63076,63078,63081,63083,63086],{"class":117,"line":118},[115,63073,2719],{"class":202},[115,63075,2380],{"class":121},[115,63077,7493],{"class":125},[115,63079,63080],{"class":132},"\"your-service.onrender.com\"",[115,63082,1153],{"class":125},[115,63084,63085],{"class":132},"\"yourdomain.com\"",[115,63087,2552],{"class":125},[115,63089,63090,63092,63094,63096,63099,63101,63104],{"class":117,"line":136},[115,63091,2725],{"class":202},[115,63093,2380],{"class":121},[115,63095,7493],{"class":125},[115,63097,63098],{"class":132},"\"https:\u002F\u002Fyour-service.onrender.com\"",[115,63100,1153],{"class":125},[115,63102,63103],{"class":132},"\"https:\u002F\u002Fyourdomain.com\"",[115,63105,2552],{"class":125},[16,63107,63108,63109,63111],{},"If you use ",[20,63110,4246],{},", add both hostnames and both trusted origins.",[16,63113,63114],{},"With Render terminating HTTPS in front of your app, these settings matter:",[106,63116,63118],{"className":2369,"code":63117,"language":1114,"meta":111,"style":111},"SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n",[20,63119,63120,63136,63144,63152],{"__ignoreMap":111},[115,63121,63122,63124,63126,63128,63130,63132,63134],{"class":117,"line":118},[115,63123,2377],{"class":202},[115,63125,2380],{"class":121},[115,63127,2383],{"class":125},[115,63129,2386],{"class":132},[115,63131,1153],{"class":125},[115,63133,2391],{"class":132},[115,63135,2394],{"class":125},[115,63137,63138,63140,63142],{"class":117,"line":136},[115,63139,2407],{"class":202},[115,63141,2380],{"class":121},[115,63143,2412],{"class":202},[115,63145,63146,63148,63150],{"class":117,"line":149},[115,63147,2417],{"class":202},[115,63149,2380],{"class":121},[115,63151,2412],{"class":202},[115,63153,63154,63156,63158],{"class":117,"line":162},[115,63155,2426],{"class":202},[115,63157,2380],{"class":121},[115,63159,2412],{"class":202},[16,63161,3515],{},[63,63163,63164,63167,63170,63172],{},[66,63165,63166],{},"custom domain resolves",[66,63168,63169],{},"HTTPS loads correctly",[66,63171,15891],{},[66,63173,24529],{},[11,63175,1321],{"id":1320},[16,63177,63178],{},"This setup works because it matches how Django should run behind a managed platform proxy:",[63,63180,63181,63184,63187,63192,63195,63200],{},[66,63182,63183],{},"Gunicorn serves the Django WSGI app",[66,63185,63186],{},"Render provides the runtime and public endpoint",[66,63188,63189,63190],{},"PostgreSQL is injected through ",[20,63191,10873],{},[66,63193,63194],{},"WhiteNoise handles static assets if you are not using external storage",[66,63196,63197,63198],{},"Django is made proxy-aware with ",[20,63199,2377],{},[66,63201,63202],{},"secrets stay outside the repository in environment variables",[16,63204,63205,63206,63208],{},"Manual dashboard setup is good for getting started. ",[20,63207,61746],{}," is better when you want repeatable infrastructure and fewer manual mistakes.",[52,63210,63212],{"id":63211},"when-manual-render-setup-becomes-repetitive","When manual Render setup becomes repetitive",[16,63214,63215,63216,63218],{},"Once you are deploying multiple environments or multiple projects, the same steps repeat: service creation, environment variable setup, build commands, migration checks, health checks, and smoke tests. That is the point where a reusable ",[20,63217,61746],{},", a hardened settings module, and a release checklist become worthwhile. Automating those parts reduces config drift more than it reduces effort.",[11,63220,1337],{"id":1336},[63,63222,63223,63228,63239,63251,63259,63265,63270,63276],{},[66,63224,63225,63227],{},[1226,63226,2920],{}," WhiteNoise is for static files, not user-uploaded media. If users upload files, plan external object storage.",[66,63229,63230,63233,63234,63236,63237,211],{},[1226,63231,63232],{},"DisallowedHost errors:"," usually caused by missing Render hostname, custom domain, or ",[20,63235,4246],{}," variant in ",[20,63238,2719],{},[66,63240,63241,63244,63245,63247,63248,63250],{},[1226,63242,63243],{},"CSRF failures after adding a domain:"," update ",[20,63246,2725],{}," with full ",[20,63249,7733],{}," origins.",[66,63252,63253,63258],{},[1226,63254,63255,63256,241],{},"Missing ",[20,63257,10873],{}," fail fast in production. Do not silently fall back to SQLite or an incomplete database config.",[66,63260,63261,63264],{},[1226,63262,63263],{},"Migration risk:"," a bad migration can break a healthy app. Prefer additive, backward-compatible schema changes.",[66,63266,63267,63269],{},[1226,63268,52937],{}," if a code change fails, redeploy the previous commit. If an environment variable change caused the issue, restore the previous value. Database rollback is harder and may require restoring from backup or applying corrective migrations.",[66,63271,63272,63275],{},[1226,63273,63274],{},"Health checks:"," keep the endpoint simple. Do not require authentication, and do not make it expensive to compute.",[66,63277,63278,63280],{},[1226,63279,27025],{}," the basic start command is enough to begin. Tune workers and timeouts later based on real traffic and memory use.",[11,63282,1386],{"id":1385},[16,63284,63285,63286,211],{},"For the settings side of this deployment, see the ",[1226,63287,63288],{},[1395,63289,32365],{"href":2999},[16,63291,63292,63293,211],{},"If you want to compare Render with a self-managed server approach, read ",[1226,63294,63295],{},[1395,63296,2986],{"href":2985},[16,63298,63299,63300,211],{},"For more detail on static file handling, see ",[1226,63301,63302],{},[1395,63303,63304],{"href":2978},"how to configure Django static files in production",[16,63306,63307,63308,211],{},"For recovery planning after a bad release, read ",[1226,63309,63310],{},[1395,63311,32225],{"href":1415},[11,63313,1420],{"id":1419},[52,63315,63317],{"id":63316},"can-i-deploy-django-on-render-without-docker","Can I deploy Django on Render without Docker?",[16,63319,63320],{},"Yes. Render supports Python web services directly, so Docker is not required for a standard Django deployment.",[52,63322,63324],{"id":63323},"do-i-need-gunicorn-on-render-for-django","Do I need Gunicorn on Render for Django?",[16,63326,63327],{},"Yes, for a typical Django deployment you should run Gunicorn as the app server:",[106,63329,63330],{"className":108,"code":60855,"language":110,"meta":111,"style":111},[20,63331,63332],{"__ignoreMap":111},[115,63333,63334,63336,63338,63340,63342],{"class":117,"line":118},[115,63335,14954],{"class":262},[115,63337,60864],{"class":132},[115,63339,23605],{"class":202},[115,63341,60869],{"class":132},[115,63343,60872],{"class":125},[52,63345,63347],{"id":63346},"how-do-i-run-migrations-on-render-safely","How do I run migrations on Render safely?",[16,63349,42059,63350,63352],{},[20,63351,38156],{}," as part of a controlled release process, not as an afterthought once the new code is already serving traffic. Prefer backward-compatible migrations so old and new app versions can tolerate the same schema during rollout.",[52,63354,63356],{"id":63355},"why-are-my-static-files-not-loading-on-render","Why are my static files not loading on Render?",[16,63358,63359],{},"Usually one of these is missing:",[63,63361,63362,63366,63370,63373],{},[66,63363,63364],{},[20,63365,11918],{},[66,63367,63368,61726],{},[20,63369,13689],{},[66,63371,63372],{},"WhiteNoise middleware and storage config",[66,63374,63375],{},"correct static file references in templates",[52,63377,1434,63379,63381],{"id":63378},"should-i-use-renderyaml-or-configure-services-manually",[20,63380,61746],{}," or configure services manually?",[16,63383,63384,63385,63387],{},"Use manual dashboard setup for a first deployment or a single app. Use ",[20,63386,61746],{}," when you want repeatability, versioned infrastructure, and lower config drift.",[1485,63389,63390],{},"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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":63392},[63393,63394,63395,63396,63402,63407,63414,63416,63417,63418,63421,63422,63423],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":61752,"depth":136,"text":61753,"children":63397},[63398,63399,63400,63401],{"id":60222,"depth":149,"text":60223},{"id":61793,"depth":149,"text":61794},{"id":62140,"depth":149,"text":62141},{"id":62261,"depth":149,"text":62262},{"id":62352,"depth":136,"text":62353,"children":63403},[63404,63405],{"id":62356,"depth":149,"text":62357},{"id":62391,"depth":149,"text":63406},"Decide between dashboard setup and render.yaml",{"id":62419,"depth":136,"text":62420,"children":63408},[63409,63410,63411,63412,63413],{"id":62423,"depth":149,"text":62424},{"id":62440,"depth":149,"text":62441},{"id":62515,"depth":149,"text":62516},{"id":62631,"depth":149,"text":62632},{"id":62661,"depth":149,"text":62662},{"id":62792,"depth":136,"text":63415},"4. Deploy Django on Render with render.yaml",{"id":62972,"depth":136,"text":62973},{"id":63057,"depth":136,"text":63058},{"id":1320,"depth":136,"text":1321,"children":63419},[63420],{"id":63211,"depth":149,"text":63212},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":63424},[63425,63426,63427,63428,63429],{"id":63316,"depth":149,"text":63317},{"id":63323,"depth":149,"text":63324},{"id":63346,"depth":149,"text":63347},{"id":63355,"depth":149,"text":63356},{"id":63378,"depth":149,"text":63430},"Should I use render.yaml or configure services manually?",{},"\u002Fdeploy-django-on-render",[60121,1403,2992],{"title":61641,"description":61648},[1557,63436,1558],"render","deploy-django-on-render",[1557,63436,1558],"mBmf7gpYtdrPOH_9MeQNHmLAwRUtlGmg4T5V7H0LL9o",{"id":63441,"title":63442,"body":63443,"category":3088,"description":65671,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":65672,"navigation":309,"path":65673,"priority":50898,"related":65674,"role":1553,"section":3098,"seo":65675,"stack":65676,"stem":65678,"tags":65679,"type":1561,"__hash__":65680},"articles\u002Fdeploy-django-on-digitalocean.md","How to Deploy Django on a DigitalOcean Droplet",{"type":8,"value":63444,"toc":65646},[63445,63447,63456,63459,63480,63483,63485,63488,63519,63522,63548,63550,63552,63556,63559,63562,63574,63577,63600,63603,63624,63626,63677,63680,63714,63719,63725,63727,63740,63742,63762,63768,63772,63775,63809,63813,63900,63903,63930,63933,64303,64306,64320,64324,64327,64382,64387,64426,64434,64437,64482,64485,64498,64504,64508,64511,64525,64528,64630,64636,64639,64682,64684,64696,64699,64717,64721,64726,64822,64825,64868,64870,64888,64892,64896,65029,65031,65065,65068,65113,65116,65191,65198,65200,65223,65229,65233,65236,65256,65259,65272,65275,65295,65298,65311,65315,65318,65417,65420,65463,65465,65485,65494,65496,65499,65501,65503,65506,65509,65518,65521,65523,65525,65572,65574,65576,65582,65587,65592,65597,65604,65606,65608,65612,65615,65619,65622,65626,65629,65633,65636,65640,65643],[11,63446,14],{"id":13},[16,63448,63449,63450,44672,63452,63455],{},"If you want to deploy Django on a DigitalOcean Droplet, you need more than ",[20,63451,11657],{},[20,63453,63454],{},"runserver"," is for development only. It is not designed for process supervision, reverse proxying, TLS termination, static file delivery, or reliable restarts after crashes or reboots.",[16,63457,63458],{},"A production-safe Django deployment on a Droplet usually needs:",[63,63460,63461,63463,63466,63469,63471,63473,63475,63477],{},[66,63462,20387],{},[66,63464,63465],{},"a non-root sudo user",[66,63467,63468],{},"Python virtual environment",[66,63470,1773],{},[66,63472,1946],{},[66,63474,1647],{},[66,63476,1277],{},[66,63478,63479],{},"HTTPS with Let's Encrypt",[16,63481,63482],{},"The goal is to host the Django app on a DigitalOcean Droplet in a way that survives restarts, serves traffic through a real web server, and keeps secrets out of source control. A basic manual deploy flow is included, with notes on safer rollback patterns for later.",[11,63484,30],{"id":29},[16,63486,63487],{},"For most projects, the simplest reliable stack is:",[63,63489,63490,63493,63498,63504,63507,63511,63514,63516],{},[66,63491,63492],{},"Ubuntu 22.04 or 24.04 on a DigitalOcean Droplet",[66,63494,63495,63496],{},"Django app in ",[20,63497,14814],{},[66,63499,63500,63501],{},"Python virtualenv in ",[20,63502,63503],{},"\u002Fsrv\u002Fmyapp\u002F.venv",[66,63505,63506],{},"PostgreSQL for the database",[66,63508,20393,63509],{},[20,63510,20396],{},[66,63512,63513],{},"Nginx reverse proxy on ports 80\u002F443",[66,63515,20408],{},[66,63517,63518],{},"Certbot issuing TLS certificates",[16,63520,63521],{},"High-level deploy flow:",[1173,63523,63524,63527,63530,63533,63536,63539,63542,63545],{},[66,63525,63526],{},"Create and harden the Droplet.",[66,63528,63529],{},"Configure Django production settings.",[66,63531,63532],{},"Install app dependencies in a virtualenv.",[66,63534,63535],{},"Set up PostgreSQL.",[66,63537,63538],{},"Run Gunicorn under systemd.",[66,63540,63541],{},"Put Nginx in front of it.",[66,63543,63544],{},"Enable HTTPS with Certbot.",[66,63546,63547],{},"Deploy code, run migrations, collect static files, restart Gunicorn, and verify logs.",[23099,63549],{},[11,63551,43],{"id":42},[11,63553,63555],{"id":63554},"_1-create-and-harden-the-digitalocean-droplet","1) Create and harden the DigitalOcean Droplet",[16,63557,63558],{},"Choose Ubuntu 22.04 LTS or 24.04 LTS. Add your SSH key during Droplet creation if possible.",[16,63560,63561],{},"SSH in as root once:",[106,63563,63565],{"className":108,"code":63564,"language":110,"meta":111,"style":111},"ssh root@your_server_ip\n",[20,63566,63567],{"__ignoreMap":111},[115,63568,63569,63571],{"class":117,"line":118},[115,63570,14184],{"class":262},[115,63572,63573],{"class":132}," root@your_server_ip\n",[16,63575,63576],{},"Create a deploy user and grant sudo:",[106,63578,63580],{"className":108,"code":63579,"language":110,"meta":111,"style":111},"adduser deploy\nusermod -aG sudo deploy\n",[20,63581,63582,63589],{"__ignoreMap":111},[115,63583,63584,63587],{"class":117,"line":118},[115,63585,63586],{"class":262},"adduser",[115,63588,14215],{"class":132},[115,63590,63591,63594,63596,63598],{"class":117,"line":136},[115,63592,63593],{"class":262},"usermod",[115,63595,14225],{"class":202},[115,63597,14228],{"class":132},[115,63599,14215],{"class":132},[16,63601,63602],{},"Set up SSH keys for that user:",[106,63604,63606],{"className":108,"code":63605,"language":110,"meta":111,"style":111},"rsync --archive --chown=deploy:deploy ~\u002F.ssh \u002Fhome\u002Fdeploy\n",[20,63607,63608],{"__ignoreMap":111},[115,63609,63610,63612,63615,63618,63621],{"class":117,"line":118},[115,63611,698],{"class":262},[115,63613,63614],{"class":202}," --archive",[115,63616,63617],{"class":202}," --chown=deploy:deploy",[115,63619,63620],{"class":132}," ~\u002F.ssh",[115,63622,63623],{"class":132}," \u002Fhome\u002Fdeploy\n",[16,63625,29052],{},[106,63627,63629],{"className":108,"code":63628,"language":110,"meta":111,"style":111},"apt update && apt upgrade -y\napt install -y python3 python3-venv python3-pip nginx postgresql postgresql-contrib certbot python3-certbot-nginx ufw git\n",[20,63630,63631,63646],{"__ignoreMap":111},[115,63632,63633,63636,63638,63640,63642,63644],{"class":117,"line":118},[115,63634,63635],{"class":262},"apt",[115,63637,14305],{"class":132},[115,63639,3912],{"class":125},[115,63641,63635],{"class":262},[115,63643,14314],{"class":132},[115,63645,14317],{"class":202},[115,63647,63648,63650,63652,63654,63656,63658,63660,63662,63664,63667,63669,63672,63674],{"class":117,"line":136},[115,63649,63635],{"class":262},[115,63651,6600],{"class":132},[115,63653,8432],{"class":202},[115,63655,14474],{"class":132},[115,63657,14477],{"class":132},[115,63659,14480],{"class":132},[115,63661,3906],{"class":132},[115,63663,14520],{"class":132},[115,63665,63666],{"class":132}," postgresql-contrib",[115,63668,6603],{"class":132},[115,63670,63671],{"class":132}," python3-certbot-nginx",[115,63673,2014],{"class":132},[115,63675,63676],{"class":132}," git\n",[16,63678,63679],{},"Configure the firewall:",[106,63681,63683],{"className":108,"code":63682,"language":110,"meta":111,"style":111},"ufw allow OpenSSH\nufw allow 'Nginx Full'\nufw enable\nufw status\n",[20,63684,63685,63694,63702,63708],{"__ignoreMap":111},[115,63686,63687,63690,63692],{"class":117,"line":118},[115,63688,63689],{"class":262},"ufw",[115,63691,14341],{"class":132},[115,63693,14344],{"class":132},[115,63695,63696,63698,63700],{"class":117,"line":136},[115,63697,63689],{"class":262},[115,63699,14341],{"class":132},[115,63701,29186],{"class":132},[115,63703,63704,63706],{"class":117,"line":149},[115,63705,63689],{"class":262},[115,63707,14375],{"class":132},[115,63709,63710,63712],{"class":117,"line":162},[115,63711,63689],{"class":262},[115,63713,2017],{"class":132},[16,63715,63716,63717,241],{},"Disable direct root SSH login in ",[20,63718,14384],{},[106,63720,63723],{"className":63721,"code":63722,"language":247,"meta":111},[245],"PermitRootLogin no\nPasswordAuthentication no\n",[20,63724,63722],{"__ignoreMap":111},[16,63726,14402],{},[106,63728,63730],{"className":108,"code":63729,"language":110,"meta":111,"style":111},"systemctl reload ssh\n",[20,63731,63732],{"__ignoreMap":111},[115,63733,63734,63736,63738],{"class":117,"line":118},[115,63735,1981],{"class":262},[115,63737,3919],{"class":132},[115,63739,14418],{"class":132},[16,63741,8572],{},[106,63743,63745],{"className":108,"code":63744,"language":110,"meta":111,"style":111},"ssh deploy@your_server_ip\nsudo ufw status\n",[20,63746,63747,63754],{"__ignoreMap":111},[115,63748,63749,63751],{"class":117,"line":118},[115,63750,14184],{"class":262},[115,63752,63753],{"class":132}," deploy@your_server_ip\n",[115,63755,63756,63758,63760],{"class":117,"line":136},[115,63757,2001],{"class":262},[115,63759,2014],{"class":132},[115,63761,2017],{"class":132},[16,63763,63764,63765,63767],{},"Rollback note: do not close your current SSH session until you confirm the ",[20,63766,3098],{}," user can log in successfully.",[11,63769,63771],{"id":63770},"_2-prepare-the-django-application-for-production","2) Prepare the Django application for production",[16,63773,63774],{},"On the server, create an environment file:",[106,63776,63778],{"className":108,"code":63777,"language":110,"meta":111,"style":111},"sudo touch \u002Fetc\u002Fmyapp.env\nsudo chown root:deploy \u002Fetc\u002Fmyapp.env\nsudo chmod 640 \u002Fetc\u002Fmyapp.env\n",[20,63779,63780,63789,63799],{"__ignoreMap":111},[115,63781,63782,63784,63787],{"class":117,"line":118},[115,63783,2001],{"class":262},[115,63785,63786],{"class":132}," touch",[115,63788,14995],{"class":132},[115,63790,63791,63793,63795,63797],{"class":117,"line":136},[115,63792,2001],{"class":262},[115,63794,6733],{"class":132},[115,63796,15052],{"class":132},[115,63798,14995],{"class":132},[115,63800,63801,63803,63805,63807],{"class":117,"line":149},[115,63802,2001],{"class":262},[115,63804,12480],{"class":132},[115,63806,12483],{"class":202},[115,63808,14995],{"class":132},[16,63810,9761,63811,241],{},[20,63812,14981],{},[106,63814,63816],{"className":108,"code":63815,"language":110,"meta":111,"style":111},"SECRET_KEY=replace-with-a-long-random-value\nDEBUG=False\nALLOWED_HOSTS=example.com,www.example.com\nCSRF_TRUSTED_ORIGINS=https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\nDATABASE_NAME=myapp\nDATABASE_USER=myappuser\nDATABASE_PASSWORD=strong-password\nDATABASE_HOST=127.0.0.1\nDATABASE_PORT=5432\n",[20,63817,63818,63827,63835,63844,63853,63862,63872,63882,63891],{"__ignoreMap":111},[115,63819,63820,63822,63824],{"class":117,"line":118},[115,63821,2713],{"class":125},[115,63823,129],{"class":121},[115,63825,63826],{"class":132},"replace-with-a-long-random-value\n",[115,63828,63829,63831,63833],{"class":117,"line":136},[115,63830,7350],{"class":125},[115,63832,129],{"class":121},[115,63834,45402],{"class":132},[115,63836,63837,63839,63841],{"class":117,"line":149},[115,63838,2719],{"class":125},[115,63840,129],{"class":121},[115,63842,63843],{"class":132},"example.com,www.example.com\n",[115,63845,63846,63848,63850],{"class":117,"line":162},[115,63847,2725],{"class":125},[115,63849,129],{"class":121},[115,63851,63852],{"class":132},"https:\u002F\u002Fexample.com,https:\u002F\u002Fwww.example.com\n",[115,63854,63855,63858,63860],{"class":117,"line":175},[115,63856,63857],{"class":125},"DATABASE_NAME",[115,63859,129],{"class":121},[115,63861,20491],{"class":132},[115,63863,63864,63867,63869],{"class":117,"line":350},[115,63865,63866],{"class":125},"DATABASE_USER",[115,63868,129],{"class":121},[115,63870,63871],{"class":132},"myappuser\n",[115,63873,63874,63877,63879],{"class":117,"line":365},[115,63875,63876],{"class":125},"DATABASE_PASSWORD",[115,63878,129],{"class":121},[115,63880,63881],{"class":132},"strong-password\n",[115,63883,63884,63887,63889],{"class":117,"line":380},[115,63885,63886],{"class":125},"DATABASE_HOST",[115,63888,129],{"class":121},[115,63890,45451],{"class":132},[115,63892,63893,63896,63898],{"class":117,"line":487},[115,63894,63895],{"class":125},"DATABASE_PORT",[115,63897,129],{"class":121},[115,63899,10464],{"class":132},[16,63901,63902],{},"Your Django settings should read from environment variables. At minimum, set:",[63,63904,63905,63909,63913,63917,63920,63924,63927],{},[66,63906,63907],{},[20,63908,32431],{},[66,63910,63911],{},[20,63912,2719],{},[66,63914,63915],{},[20,63916,2725],{},[66,63918,63919],{},"PostgreSQL database settings",[66,63921,63922],{},[20,63923,11918],{},[66,63925,63926],{},"secret key from environment",[66,63928,63929],{},"secure proxy and cookie settings for HTTPS",[16,63931,63932],{},"Example settings pattern:",[106,63934,63936],{"className":2369,"code":63935,"language":1114,"meta":111,"style":111},"import os\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = os.environ.get(\"DEBUG\", \"False\") == \"True\"\nALLOWED_HOSTS = [h for h in os.environ.get(\"ALLOWED_HOSTS\", \"\").split(\",\") if h]\nCSRF_TRUSTED_ORIGINS = [u for u in os.environ.get(\"CSRF_TRUSTED_ORIGINS\", \"\").split(\",\") if u]\n\nSECRET_KEY = os.environ[\"SECRET_KEY\"]\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": os.environ[\"DATABASE_NAME\"],\n        \"USER\": os.environ[\"DATABASE_USER\"],\n        \"PASSWORD\": os.environ[\"DATABASE_PASSWORD\"],\n        \"HOST\": os.environ.get(\"DATABASE_HOST\", \"127.0.0.1\"),\n        \"PORT\": os.environ.get(\"DATABASE_PORT\", \"5432\"),\n    }\n}\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = BASE_DIR \u002F \"media\"\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\n# Enable HSTS only after HTTPS is working correctly everywhere.\nSECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = False\n",[20,63937,63938,63944,63954,63958,63970,63974,63995,64027,64061,64065,64077,64081,64089,64095,64105,64116,64127,64138,64154,64169,64173,64177,64181,64189,64201,64205,64213,64226,64230,64246,64254,64262,64270,64274,64279,64287,64295],{"__ignoreMap":111},[115,63939,63940,63942],{"class":117,"line":118},[115,63941,5613],{"class":121},[115,63943,5616],{"class":125},[115,63945,63946,63948,63950,63952],{"class":117,"line":136},[115,63947,5621],{"class":121},[115,63949,16353],{"class":125},[115,63951,5613],{"class":121},[115,63953,16358],{"class":125},[115,63955,63956],{"class":117,"line":149},[115,63957,310],{"emptyLinePlaceholder":309},[115,63959,63960,63962,63964,63966,63968],{"class":117,"line":162},[115,63961,16374],{"class":202},[115,63963,2380],{"class":121},[115,63965,16379],{"class":125},[115,63967,16382],{"class":202},[115,63969,34339],{"class":125},[115,63971,63972],{"class":117,"line":175},[115,63973,310],{"emptyLinePlaceholder":309},[115,63975,63976,63978,63980,63982,63984,63986,63988,63990,63992],{"class":117,"line":350},[115,63977,7350],{"class":202},[115,63979,2380],{"class":121},[115,63981,8884],{"class":125},[115,63983,60356],{"class":132},[115,63985,1153],{"class":125},[115,63987,25169],{"class":132},[115,63989,18281],{"class":125},[115,63991,25175],{"class":121},[115,63993,63994],{"class":132}," \"True\"\n",[115,63996,63997,63999,64001,64003,64005,64007,64009,64011,64013,64015,64017,64019,64021,64023,64025],{"class":117,"line":365},[115,63998,2719],{"class":202},[115,64000,2380],{"class":121},[115,64002,25191],{"class":125},[115,64004,18256],{"class":121},[115,64006,18259],{"class":125},[115,64008,18262],{"class":121},[115,64010,8884],{"class":125},[115,64012,18267],{"class":132},[115,64014,1153],{"class":125},[115,64016,18272],{"class":132},[115,64018,18275],{"class":125},[115,64020,18278],{"class":132},[115,64022,18281],{"class":125},[115,64024,10833],{"class":121},[115,64026,25217],{"class":125},[115,64028,64029,64031,64033,64036,64038,64040,64042,64044,64046,64048,64050,64052,64054,64056,64058],{"class":117,"line":380},[115,64030,2725],{"class":202},[115,64032,2380],{"class":121},[115,64034,64035],{"class":125}," [u ",[115,64037,18256],{"class":121},[115,64039,29460],{"class":125},[115,64041,18262],{"class":121},[115,64043,8884],{"class":125},[115,64045,18307],{"class":132},[115,64047,1153],{"class":125},[115,64049,18272],{"class":132},[115,64051,18275],{"class":125},[115,64053,18278],{"class":132},[115,64055,18281],{"class":125},[115,64057,10833],{"class":121},[115,64059,64060],{"class":125}," u]\n",[115,64062,64063],{"class":117,"line":487},[115,64064,310],{"emptyLinePlaceholder":309},[115,64066,64067,64069,64071,64073,64075],{"class":117,"line":2095},[115,64068,2713],{"class":202},[115,64070,2380],{"class":121},[115,64072,8861],{"class":125},[115,64074,16412],{"class":132},[115,64076,2552],{"class":125},[115,64078,64079],{"class":117,"line":2104},[115,64080,310],{"emptyLinePlaceholder":309},[115,64082,64083,64085,64087],{"class":117,"line":2113},[115,64084,10632],{"class":202},[115,64086,2380],{"class":121},[115,64088,2166],{"class":125},[115,64090,64091,64093],{"class":117,"line":2122},[115,64092,10664],{"class":132},[115,64094,3374],{"class":125},[115,64096,64097,64099,64101,64103],{"class":117,"line":2131},[115,64098,10671],{"class":132},[115,64100,2513],{"class":125},[115,64102,10676],{"class":132},[115,64104,3354],{"class":125},[115,64106,64107,64109,64111,64114],{"class":117,"line":2136},[115,64108,10683],{"class":132},[115,64110,10686],{"class":125},[115,64112,64113],{"class":132},"\"DATABASE_NAME\"",[115,64115,3430],{"class":125},[115,64117,64118,64120,64122,64125],{"class":117,"line":2142},[115,64119,10696],{"class":132},[115,64121,10686],{"class":125},[115,64123,64124],{"class":132},"\"DATABASE_USER\"",[115,64126,3430],{"class":125},[115,64128,64129,64131,64133,64136],{"class":117,"line":2273},[115,64130,10708],{"class":132},[115,64132,10686],{"class":125},[115,64134,64135],{"class":132},"\"DATABASE_PASSWORD\"",[115,64137,3430],{"class":125},[115,64139,64140,64142,64144,64147,64149,64152],{"class":117,"line":2282},[115,64141,10720],{"class":132},[115,64143,10735],{"class":125},[115,64145,64146],{"class":132},"\"DATABASE_HOST\"",[115,64148,1153],{"class":125},[115,64150,64151],{"class":132},"\"127.0.0.1\"",[115,64153,10746],{"class":125},[115,64155,64156,64158,64160,64163,64165,64167],{"class":117,"line":2291},[115,64157,10732],{"class":132},[115,64159,10735],{"class":125},[115,64161,64162],{"class":132},"\"DATABASE_PORT\"",[115,64164,1153],{"class":125},[115,64166,10743],{"class":132},[115,64168,10746],{"class":125},[115,64170,64171],{"class":117,"line":2299},[115,64172,2233],{"class":125},[115,64174,64175],{"class":117,"line":2307},[115,64176,2323],{"class":125},[115,64178,64179],{"class":117,"line":2315},[115,64180,310],{"emptyLinePlaceholder":309},[115,64182,64183,64185,64187],{"class":117,"line":2320},[115,64184,11908],{"class":202},[115,64186,2380],{"class":121},[115,64188,11913],{"class":132},[115,64190,64191,64193,64195,64197,64199],{"class":117,"line":7083},[115,64192,11918],{"class":202},[115,64194,2380],{"class":121},[115,64196,11923],{"class":202},[115,64198,11926],{"class":121},[115,64200,11929],{"class":132},[115,64202,64203],{"class":117,"line":7090},[115,64204,310],{"emptyLinePlaceholder":309},[115,64206,64207,64209,64211],{"class":117,"line":7097},[115,64208,15204],{"class":202},[115,64210,2380],{"class":121},[115,64212,15209],{"class":132},[115,64214,64215,64217,64219,64221,64223],{"class":117,"line":7108},[115,64216,15214],{"class":202},[115,64218,2380],{"class":121},[115,64220,11923],{"class":202},[115,64222,11926],{"class":121},[115,64224,64225],{"class":132}," \"media\"\n",[115,64227,64228],{"class":117,"line":7113},[115,64229,310],{"emptyLinePlaceholder":309},[115,64231,64232,64234,64236,64238,64240,64242,64244],{"class":117,"line":16535},[115,64233,2377],{"class":202},[115,64235,2380],{"class":121},[115,64237,2383],{"class":125},[115,64239,2386],{"class":132},[115,64241,1153],{"class":125},[115,64243,2391],{"class":132},[115,64245,2394],{"class":125},[115,64247,64248,64250,64252],{"class":117,"line":16544},[115,64249,2407],{"class":202},[115,64251,2380],{"class":121},[115,64253,2412],{"class":202},[115,64255,64256,64258,64260],{"class":117,"line":16549},[115,64257,2417],{"class":202},[115,64259,2380],{"class":121},[115,64261,2412],{"class":202},[115,64263,64264,64266,64268],{"class":117,"line":16555},[115,64265,2426],{"class":202},[115,64267,2380],{"class":121},[115,64269,2412],{"class":202},[115,64271,64272],{"class":117,"line":16564},[115,64273,310],{"emptyLinePlaceholder":309},[115,64275,64276],{"class":117,"line":16573},[115,64277,64278],{"class":3861},"# Enable HSTS only after HTTPS is working correctly everywhere.\n",[115,64280,64281,64283,64285],{"class":117,"line":16582},[115,64282,7440],{"class":202},[115,64284,2380],{"class":121},[115,64286,11991],{"class":202},[115,64288,64289,64291,64293],{"class":117,"line":16587},[115,64290,7464],{"class":202},[115,64292,2380],{"class":121},[115,64294,2412],{"class":202},[115,64296,64297,64299,64301],{"class":117,"line":16596},[115,64298,12004],{"class":202},[115,64300,2380],{"class":121},[115,64302,7355],{"class":202},[16,64304,64305],{},"Run Django’s deployment checks later after dependencies are installed:",[106,64307,64308],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,64309,64310],{"__ignoreMap":111},[115,64311,64312,64314,64316,64318],{"class":117,"line":118},[115,64313,1114],{"class":262},[115,64315,1117],{"class":132},[115,64317,1814],{"class":132},[115,64319,1817],{"class":202},[11,64321,64323],{"id":64322},"_3-create-the-python-environment-and-install-app-dependencies","3) Create the Python environment and install app dependencies",[16,64325,64326],{},"Switch to the deploy user:",[106,64328,64330],{"className":108,"code":64329,"language":110,"meta":111,"style":111},"ssh deploy@your_server_ip\nmkdir -p \u002Fsrv\u002Fmyapp\ncd \u002Fsrv\u002Fmyapp\npython3 -m venv .venv\nsource .venv\u002Fbin\u002Factivate\npip install -U pip wheel\n",[20,64331,64332,64338,64346,64352,64362,64368],{"__ignoreMap":111},[115,64333,64334,64336],{"class":117,"line":118},[115,64335,14184],{"class":262},[115,64337,63753],{"class":132},[115,64339,64340,64342,64344],{"class":117,"line":136},[115,64341,12314],{"class":262},[115,64343,1001],{"class":202},[115,64345,14799],{"class":132},[115,64347,64348,64350],{"class":117,"line":149},[115,64349,5303],{"class":202},[115,64351,14799],{"class":132},[115,64353,64354,64356,64358,64360],{"class":117,"line":162},[115,64355,12281],{"class":262},[115,64357,12284],{"class":202},[115,64359,12287],{"class":132},[115,64361,12290],{"class":132},[115,64363,64364,64366],{"class":117,"line":175},[115,64365,5311],{"class":202},[115,64367,12297],{"class":132},[115,64369,64370,64372,64374,64376,64379],{"class":117,"line":350},[115,64371,8618],{"class":262},[115,64373,6600],{"class":132},[115,64375,1010],{"class":202},[115,64377,64378],{"class":132}," pip",[115,64380,64381],{"class":132}," wheel\n",[16,64383,64384,64385,241],{},"Deploy your code into ",[20,64386,14814],{},[106,64388,64390],{"className":108,"code":64389,"language":110,"meta":111,"style":111},"git clone https:\u002F\u002Fyour-repository-url \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\ncd \u002Fsrv\u002Fmyapp\u002Fcurrent\npip install -r requirements.txt\n",[20,64391,64392,64403,64410,64416],{"__ignoreMap":111},[115,64393,64394,64396,64398,64401],{"class":117,"line":118},[115,64395,13525],{"class":262},[115,64397,14843],{"class":132},[115,64399,64400],{"class":132}," https:\u002F\u002Fyour-repository-url",[115,64402,5306],{"class":132},[115,64404,64405,64407],{"class":117,"line":136},[115,64406,5311],{"class":202},[115,64408,64409],{"class":132}," \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\n",[115,64411,64412,64414],{"class":117,"line":149},[115,64413,5303],{"class":202},[115,64415,5306],{"class":132},[115,64417,64418,64420,64422,64424],{"class":117,"line":162},[115,64419,8618],{"class":262},[115,64421,6600],{"class":132},[115,64423,12350],{"class":202},[115,64425,12353],{"class":132},[16,64427,64428,64429,4493,64431,64433],{},"If you use PostgreSQL from Django, ensure your requirements include a PostgreSQL driver such as ",[20,64430,60290],{},[20,64432,10587],{},", depending on your project.",[16,64435,64436],{},"Test Gunicorn directly before adding systemd:",[106,64438,64440],{"className":108,"code":64439,"language":110,"meta":111,"style":111},"source \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\ncd \u002Fsrv\u002Fmyapp\u002Fcurrent\nset -a\n. \u002Fetc\u002Fmyapp.env\nset +a\ngunicorn --bind 127.0.0.1:8000 myproject.wsgi:application\n",[20,64441,64442,64448,64454,64460,64466,64472],{"__ignoreMap":111},[115,64443,64444,64446],{"class":117,"line":118},[115,64445,5311],{"class":202},[115,64447,64409],{"class":132},[115,64449,64450,64452],{"class":117,"line":136},[115,64451,5303],{"class":202},[115,64453,5306],{"class":132},[115,64455,64456,64458],{"class":117,"line":149},[115,64457,203],{"class":202},[115,64459,206],{"class":202},[115,64461,64462,64464],{"class":117,"line":162},[115,64463,211],{"class":202},[115,64465,14995],{"class":132},[115,64467,64468,64470],{"class":117,"line":175},[115,64469,203],{"class":202},[115,64471,221],{"class":132},[115,64473,64474,64476,64478,64480],{"class":117,"line":350},[115,64475,14954],{"class":262},[115,64477,23605],{"class":202},[115,64479,23608],{"class":132},[115,64481,57467],{"class":132},[16,64483,64484],{},"In another terminal:",[106,64486,64488],{"className":108,"code":64487,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002F127.0.0.1:8000\n",[20,64489,64490],{"__ignoreMap":111},[115,64491,64492,64494,64496],{"class":117,"line":118},[115,64493,2764],{"class":262},[115,64495,2767],{"class":202},[115,64497,51205],{"class":132},[16,64499,64500,64501,211],{},"Stop Gunicorn with ",[20,64502,64503],{},"Ctrl+C",[11,64505,64507],{"id":64506},"_4-set-up-postgresql","4) Set up PostgreSQL",[16,64509,64510],{},"If PostgreSQL runs on the same Droplet:",[106,64512,64513],{"className":108,"code":14587,"language":110,"meta":111,"style":111},[20,64514,64515],{"__ignoreMap":111},[115,64516,64517,64519,64521,64523],{"class":117,"line":118},[115,64518,2001],{"class":262},[115,64520,2788],{"class":202},[115,64522,14598],{"class":132},[115,64524,14601],{"class":132},[16,64526,64527],{},"Create the database and user:",[106,64529,64531],{"className":11064,"code":64530,"language":11066,"meta":111,"style":111},"CREATE DATABASE myapp;\nCREATE USER myappuser WITH PASSWORD 'strong-password';\nALTER ROLE myappuser SET client_encoding TO 'utf8';\nALTER ROLE myappuser SET default_transaction_isolation TO 'read committed';\nALTER ROLE myappuser SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE myapp TO myappuser;\n",[20,64532,64533,64543,64560,64578,64596,64614],{"__ignoreMap":111},[115,64534,64535,64537,64539,64541],{"class":117,"line":118},[115,64536,14611],{"class":121},[115,64538,14614],{"class":121},[115,64540,5325],{"class":262},[115,64542,3811],{"class":125},[115,64544,64545,64547,64549,64551,64553,64555,64558],{"class":117,"line":136},[115,64546,14611],{"class":121},[115,64548,14625],{"class":121},[115,64550,14628],{"class":262},[115,64552,14631],{"class":121},[115,64554,14634],{"class":121},[115,64556,64557],{"class":132}," 'strong-password'",[115,64559,3811],{"class":125},[115,64561,64562,64564,64566,64568,64570,64572,64574,64576],{"class":117,"line":149},[115,64563,14644],{"class":121},[115,64565,14647],{"class":121},[115,64567,14650],{"class":125},[115,64569,14653],{"class":121},[115,64571,14656],{"class":125},[115,64573,14659],{"class":121},[115,64575,14662],{"class":132},[115,64577,3811],{"class":125},[115,64579,64580,64582,64584,64586,64588,64590,64592,64594],{"class":117,"line":162},[115,64581,14644],{"class":121},[115,64583,14647],{"class":121},[115,64585,14650],{"class":125},[115,64587,14653],{"class":121},[115,64589,14677],{"class":125},[115,64591,14659],{"class":121},[115,64593,14682],{"class":132},[115,64595,3811],{"class":125},[115,64597,64598,64600,64602,64604,64606,64608,64610,64612],{"class":117,"line":175},[115,64599,14644],{"class":121},[115,64601,14647],{"class":121},[115,64603,14650],{"class":125},[115,64605,14653],{"class":121},[115,64607,14697],{"class":125},[115,64609,14659],{"class":121},[115,64611,14702],{"class":132},[115,64613,3811],{"class":125},[115,64615,64616,64618,64620,64622,64624,64626,64628],{"class":117,"line":350},[115,64617,14709],{"class":121},[115,64619,14712],{"class":125},[115,64621,14715],{"class":121},[115,64623,14614],{"class":121},[115,64625,14720],{"class":125},[115,64627,14659],{"class":121},[115,64629,14725],{"class":125},[16,64631,64632,64633,211],{},"Exit with ",[20,64634,64635],{},"\\q",[16,64637,64638],{},"Apply migrations:",[106,64640,64642],{"className":108,"code":64641,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\nset -a\n. \u002Fetc\u002Fmyapp.env\nset +a\npython manage.py migrate\n",[20,64643,64644,64650,64656,64662,64668,64674],{"__ignoreMap":111},[115,64645,64646,64648],{"class":117,"line":118},[115,64647,5303],{"class":202},[115,64649,5306],{"class":132},[115,64651,64652,64654],{"class":117,"line":136},[115,64653,5311],{"class":202},[115,64655,64409],{"class":132},[115,64657,64658,64660],{"class":117,"line":149},[115,64659,203],{"class":202},[115,64661,206],{"class":202},[115,64663,64664,64666],{"class":117,"line":162},[115,64665,211],{"class":202},[115,64667,14995],{"class":132},[115,64669,64670,64672],{"class":117,"line":175},[115,64671,203],{"class":202},[115,64673,221],{"class":132},[115,64675,64676,64678,64680],{"class":117,"line":350},[115,64677,1114],{"class":262},[115,64679,1117],{"class":132},[115,64681,11324],{"class":132},[16,64683,8572],{},[106,64685,64686],{"className":108,"code":11330,"language":110,"meta":111,"style":111},[20,64687,64688],{"__ignoreMap":111},[115,64689,64690,64692,64694],{"class":117,"line":118},[115,64691,1114],{"class":262},[115,64693,1117],{"class":132},[115,64695,1129],{"class":132},[16,64697,64698],{},"Rollback note: if a migration fails, stop and fix it before continuing. For risky schema changes, take a PostgreSQL backup first:",[106,64700,64702],{"className":108,"code":64701,"language":110,"meta":111,"style":111},"pg_dump -Fc myapp > myapp-before-migration.dump\n",[20,64703,64704],{"__ignoreMap":111},[115,64705,64706,64708,64710,64712,64714],{"class":117,"line":118},[115,64707,22],{"class":262},[115,64709,39619],{"class":202},[115,64711,5325],{"class":132},[115,64713,604],{"class":121},[115,64715,64716],{"class":132}," myapp-before-migration.dump\n",[11,64718,64720],{"id":64719},"_5-configure-gunicorn-with-systemd","5) Configure Gunicorn with systemd",[16,64722,8628,64723,241],{},[20,64724,64725],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service",[106,64727,64729],{"className":2026,"code":64728,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=gunicorn daemon for Django app\nAfter=network.target\n\n[Service]\nUser=deploy\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp.env\nEnvironment=\"PATH=\u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\"\nExecStart=\u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Fgunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\n",[20,64730,64731,64735,64742,64748,64752,64756,64762,64768,64774,64780,64789,64796,64802,64808,64812,64816],{"__ignoreMap":111},[115,64732,64733],{"class":117,"line":118},[115,64734,2035],{"class":262},[115,64736,64737,64739],{"class":117,"line":136},[115,64738,2040],{"class":121},[115,64740,64741],{"class":125},"=gunicorn daemon for Django app\n",[115,64743,64744,64746],{"class":117,"line":149},[115,64745,2048],{"class":121},[115,64747,2051],{"class":125},[115,64749,64750],{"class":117,"line":162},[115,64751,310],{"emptyLinePlaceholder":309},[115,64753,64754],{"class":117,"line":175},[115,64755,2060],{"class":262},[115,64757,64758,64760],{"class":117,"line":350},[115,64759,2065],{"class":121},[115,64761,12548],{"class":125},[115,64763,64764,64766],{"class":117,"line":365},[115,64765,2073],{"class":121},[115,64767,2076],{"class":125},[115,64769,64770,64772],{"class":117,"line":380},[115,64771,2081],{"class":121},[115,64773,4905],{"class":125},[115,64775,64776,64778],{"class":117,"line":487},[115,64777,2089],{"class":121},[115,64779,2092],{"class":125},[115,64781,64782,64784,64786],{"class":117,"line":2095},[115,64783,36637],{"class":121},[115,64785,129],{"class":125},[115,64787,64788],{"class":132},"\"PATH=\u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\"\n",[115,64790,64791,64793],{"class":117,"line":2104},[115,64792,2107],{"class":121},[115,64794,64795],{"class":125},"=\u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Fgunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application\n",[115,64797,64798,64800],{"class":117,"line":2113},[115,64799,2116],{"class":121},[115,64801,4932],{"class":125},[115,64803,64804,64806],{"class":117,"line":2122},[115,64805,2125],{"class":121},[115,64807,12611],{"class":125},[115,64809,64810],{"class":117,"line":2131},[115,64811,310],{"emptyLinePlaceholder":309},[115,64813,64814],{"class":117,"line":2136},[115,64815,2139],{"class":262},[115,64817,64818,64820],{"class":117,"line":2142},[115,64819,2145],{"class":121},[115,64821,2148],{"class":125},[16,64823,64824],{},"Reload systemd and start the service:",[106,64826,64828],{"className":108,"code":64827,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl start gunicorn\nsudo systemctl enable gunicorn\nsudo systemctl status gunicorn\n",[20,64829,64830,64838,64848,64858],{"__ignoreMap":111},[115,64831,64832,64834,64836],{"class":117,"line":118},[115,64833,2001],{"class":262},[115,64835,3480],{"class":132},[115,64837,4984],{"class":132},[115,64839,64840,64842,64844,64846],{"class":117,"line":136},[115,64841,2001],{"class":262},[115,64843,3480],{"class":132},[115,64845,15489],{"class":132},[115,64847,1987],{"class":132},[115,64849,64850,64852,64854,64856],{"class":117,"line":149},[115,64851,2001],{"class":262},[115,64853,3480],{"class":132},[115,64855,8567],{"class":132},[115,64857,1987],{"class":132},[115,64859,64860,64862,64864,64866],{"class":117,"line":162},[115,64861,2001],{"class":262},[115,64863,3480],{"class":132},[115,64865,1984],{"class":132},[115,64867,1987],{"class":132},[16,64869,12724],{},[106,64871,64872],{"className":108,"code":3266,"language":110,"meta":111,"style":111},[20,64873,64874],{"__ignoreMap":111},[115,64875,64876,64878,64880,64882,64884,64886],{"class":117,"line":118},[115,64877,2785],{"class":262},[115,64879,2788],{"class":202},[115,64881,2791],{"class":132},[115,64883,2794],{"class":202},[115,64885,2797],{"class":202},[115,64887,2800],{"class":202},[11,64889,64891],{"id":64890},"_6-configure-nginx-as-reverse-proxy","6) Configure Nginx as reverse proxy",[16,64893,8628,64894,241],{},[20,64895,15554],{},[106,64897,64899],{"className":2154,"code":64898,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    client_max_body_size 10M;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,64900,64901,64907,64915,64921,64925,64933,64937,64945,64952,64956,64960,64968,64975,64979,64983,64991,64997,65003,65009,65015,65021,65025],{"__ignoreMap":111},[115,64902,64903,64905],{"class":117,"line":118},[115,64904,2163],{"class":121},[115,64906,2166],{"class":125},[115,64908,64909,64911,64913],{"class":117,"line":136},[115,64910,2171],{"class":121},[115,64912,3808],{"class":202},[115,64914,3811],{"class":125},[115,64916,64917,64919],{"class":117,"line":149},[115,64918,2182],{"class":121},[115,64920,3713],{"class":125},[115,64922,64923],{"class":117,"line":162},[115,64924,310],{"emptyLinePlaceholder":309},[115,64926,64927,64929,64931],{"class":117,"line":175},[115,64928,6987],{"class":121},[115,64930,12827],{"class":202},[115,64932,3811],{"class":125},[115,64934,64935],{"class":117,"line":350},[115,64936,310],{"emptyLinePlaceholder":309},[115,64938,64939,64941,64943],{"class":117,"line":365},[115,64940,2214],{"class":121},[115,64942,2217],{"class":262},[115,64944,2220],{"class":125},[115,64946,64947,64949],{"class":117,"line":380},[115,64948,2225],{"class":121},[115,64950,64951],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fstaticfiles\u002F;\n",[115,64953,64954],{"class":117,"line":487},[115,64955,2233],{"class":125},[115,64957,64958],{"class":117,"line":2095},[115,64959,310],{"emptyLinePlaceholder":309},[115,64961,64962,64964,64966],{"class":117,"line":2104},[115,64963,2214],{"class":121},[115,64965,2244],{"class":262},[115,64967,2220],{"class":125},[115,64969,64970,64972],{"class":117,"line":2113},[115,64971,2225],{"class":121},[115,64973,64974],{"class":125},"\u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fmedia\u002F;\n",[115,64976,64977],{"class":117,"line":2122},[115,64978,2233],{"class":125},[115,64980,64981],{"class":117,"line":2131},[115,64982,310],{"emptyLinePlaceholder":309},[115,64984,64985,64987,64989],{"class":117,"line":2136},[115,64986,2214],{"class":121},[115,64988,2268],{"class":262},[115,64990,2220],{"class":125},[115,64992,64993,64995],{"class":117,"line":2142},[115,64994,2276],{"class":121},[115,64996,3748],{"class":125},[115,64998,64999,65001],{"class":117,"line":2273},[115,65000,2285],{"class":121},[115,65002,2288],{"class":125},[115,65004,65005,65007],{"class":117,"line":2282},[115,65006,2285],{"class":121},[115,65008,3767],{"class":125},[115,65010,65011,65013],{"class":117,"line":2291},[115,65012,2285],{"class":121},[115,65014,2312],{"class":125},[115,65016,65017,65019],{"class":117,"line":2299},[115,65018,2285],{"class":121},[115,65020,2304],{"class":125},[115,65022,65023],{"class":117,"line":2307},[115,65024,2233],{"class":125},[115,65026,65027],{"class":117,"line":2315},[115,65028,2323],{"class":125},[16,65030,15693],{},[106,65032,65033],{"className":108,"code":30020,"language":110,"meta":111,"style":111},[20,65034,65035,65047,65055],{"__ignoreMap":111},[115,65036,65037,65039,65041,65043,65045],{"class":117,"line":118},[115,65038,2001],{"class":262},[115,65040,13105],{"class":132},[115,65042,549],{"class":202},[115,65044,15709],{"class":132},[115,65046,15712],{"class":132},[115,65048,65049,65051,65053],{"class":117,"line":136},[115,65050,2001],{"class":262},[115,65052,3906],{"class":132},[115,65054,4282],{"class":202},[115,65056,65057,65059,65061,65063],{"class":117,"line":149},[115,65058,2001],{"class":262},[115,65060,3480],{"class":132},[115,65062,3919],{"class":132},[115,65064,1996],{"class":132},[16,65066,65067],{},"Collect static files:",[106,65069,65071],{"className":108,"code":65070,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\nset -a\n. \u002Fetc\u002Fmyapp.env\nset +a\npython manage.py collectstatic --noinput\n",[20,65072,65073,65079,65085,65091,65097,65103],{"__ignoreMap":111},[115,65074,65075,65077],{"class":117,"line":118},[115,65076,5303],{"class":202},[115,65078,5306],{"class":132},[115,65080,65081,65083],{"class":117,"line":136},[115,65082,5311],{"class":202},[115,65084,64409],{"class":132},[115,65086,65087,65089],{"class":117,"line":149},[115,65088,203],{"class":202},[115,65090,206],{"class":202},[115,65092,65093,65095],{"class":117,"line":162},[115,65094,211],{"class":202},[115,65096,14995],{"class":132},[115,65098,65099,65101],{"class":117,"line":175},[115,65100,203],{"class":202},[115,65102,221],{"class":132},[115,65104,65105,65107,65109,65111],{"class":117,"line":350},[115,65106,1114],{"class":262},[115,65108,1117],{"class":132},[115,65110,1838],{"class":132},[115,65112,1841],{"class":202},[16,65114,65115],{},"Make sure Nginx can read static files, and media too if you serve uploads from local disk:",[106,65117,65119],{"className":108,"code":65118,"language":110,"meta":111,"style":111},"sudo chmod o+rx \u002Fsrv \u002Fsrv\u002Fmyapp \u002Fsrv\u002Fmyapp\u002Fcurrent\nsudo find \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fstaticfiles -type d -exec chmod 755 {} \\;\nsudo find \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fstaticfiles -type f -exec chmod 644 {} \\;\n",[20,65120,65121,65138,65167],{"__ignoreMap":111},[115,65122,65123,65125,65127,65130,65133,65136],{"class":117,"line":118},[115,65124,2001],{"class":262},[115,65126,12480],{"class":132},[115,65128,65129],{"class":132}," o+rx",[115,65131,65132],{"class":132}," \u002Fsrv",[115,65134,65135],{"class":132}," \u002Fsrv\u002Fmyapp",[115,65137,5306],{"class":132},[115,65139,65140,65142,65145,65148,65151,65154,65157,65159,65161,65164],{"class":117,"line":136},[115,65141,2001],{"class":262},[115,65143,65144],{"class":132}," find",[115,65146,65147],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fstaticfiles",[115,65149,65150],{"class":202}," -type",[115,65152,65153],{"class":132}," d",[115,65155,65156],{"class":202}," -exec",[115,65158,12480],{"class":132},[115,65160,48409],{"class":202},[115,65162,65163],{"class":132}," {}",[115,65165,65166],{"class":202}," \\;\n",[115,65168,65169,65171,65173,65175,65177,65180,65182,65184,65187,65189],{"class":117,"line":149},[115,65170,2001],{"class":262},[115,65172,65144],{"class":132},[115,65174,65147],{"class":132},[115,65176,65150],{"class":202},[115,65178,65179],{"class":132}," f",[115,65181,65156],{"class":202},[115,65183,12480],{"class":132},[115,65185,65186],{"class":202}," 644",[115,65188,65163],{"class":132},[115,65190,65166],{"class":202},[16,65192,65193,65194,65197],{},"If you serve user uploads from ",[20,65195,65196],{},"\u002Fsrv\u002Fmyapp\u002Fcurrent\u002Fmedia",", apply equivalent readable permissions there as well.",[16,65199,8572],{},[106,65201,65203],{"className":108,"code":65202,"language":110,"meta":111,"style":111},"curl -I http:\u002F\u002Fexample.com\nsudo systemctl status nginx\n",[20,65204,65205,65213],{"__ignoreMap":111},[115,65206,65207,65209,65211],{"class":117,"line":118},[115,65208,2764],{"class":262},[115,65210,2767],{"class":202},[115,65212,6494],{"class":132},[115,65214,65215,65217,65219,65221],{"class":117,"line":136},[115,65216,2001],{"class":262},[115,65218,3480],{"class":132},[115,65220,1984],{"class":132},[115,65222,1996],{"class":132},[16,65224,65225,65226,65228],{},"Rollback note: if ",[20,65227,7611],{}," fails, restore the previous config before reloading.",[11,65230,65232],{"id":65231},"_7-enable-https-with-lets-encrypt","7) Enable HTTPS with Let's Encrypt",[16,65234,65235],{},"Make sure your domain points to the Droplet IP first. Then run:",[106,65237,65238],{"className":108,"code":6676,"language":110,"meta":111,"style":111},[20,65239,65240],{"__ignoreMap":111},[115,65241,65242,65244,65246,65248,65250,65252,65254],{"class":117,"line":118},[115,65243,2001],{"class":262},[115,65245,6603],{"class":132},[115,65247,6687],{"class":202},[115,65249,1019],{"class":202},[115,65251,6434],{"class":132},[115,65253,1019],{"class":202},[115,65255,6696],{"class":132},[16,65257,65258],{},"Confirm renewal timer:",[106,65260,65262],{"className":108,"code":65261,"language":110,"meta":111,"style":111},"systemctl status certbot.timer\n",[20,65263,65264],{"__ignoreMap":111},[115,65265,65266,65268,65270],{"class":117,"line":118},[115,65267,1981],{"class":262},[115,65269,1984],{"class":132},[115,65271,7799],{"class":132},[16,65273,65274],{},"Verify HTTPS and redirect behavior:",[106,65276,65277],{"className":108,"code":13228,"language":110,"meta":111,"style":111},[20,65278,65279,65287],{"__ignoreMap":111},[115,65280,65281,65283,65285],{"class":117,"line":118},[115,65282,2764],{"class":262},[115,65284,2767],{"class":202},[115,65286,6494],{"class":132},[115,65288,65289,65291,65293],{"class":117,"line":136},[115,65290,2764],{"class":262},[115,65292,2767],{"class":202},[115,65294,2770],{"class":132},[16,65296,65297],{},"At this point, verify that:",[63,65299,65300,65302,65305,65308],{},[66,65301,13434],{},[66,65303,65304],{},"secure pages load without certificate warnings",[66,65306,65307],{},"login and form submissions still work",[66,65309,65310],{},"cookies are marked secure in the browser",[11,65312,65314],{"id":65313},"_8-deploy-application-updates","8) Deploy application updates",[16,65316,65317],{},"A simple manual update flow looks like this:",[106,65319,65321],{"className":108,"code":65320,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\ngit pull\nsource \u002Fsrv\u002Fmyapp\u002F.venv\u002Fbin\u002Factivate\npip install -r requirements.txt\nset -a\n. \u002Fetc\u002Fmyapp.env\nset +a\npython manage.py check --deploy\npython manage.py migrate\npython manage.py collectstatic --noinput\nsudo systemctl restart gunicorn\nsudo systemctl reload nginx\n",[20,65322,65323,65329,65335,65341,65351,65357,65363,65369,65379,65387,65397,65407],{"__ignoreMap":111},[115,65324,65325,65327],{"class":117,"line":118},[115,65326,5303],{"class":202},[115,65328,5306],{"class":132},[115,65330,65331,65333],{"class":117,"line":136},[115,65332,13525],{"class":262},[115,65334,13528],{"class":132},[115,65336,65337,65339],{"class":117,"line":149},[115,65338,5311],{"class":202},[115,65340,64409],{"class":132},[115,65342,65343,65345,65347,65349],{"class":117,"line":162},[115,65344,8618],{"class":262},[115,65346,6600],{"class":132},[115,65348,12350],{"class":202},[115,65350,12353],{"class":132},[115,65352,65353,65355],{"class":117,"line":175},[115,65354,203],{"class":202},[115,65356,206],{"class":202},[115,65358,65359,65361],{"class":117,"line":350},[115,65360,211],{"class":202},[115,65362,14995],{"class":132},[115,65364,65365,65367],{"class":117,"line":365},[115,65366,203],{"class":202},[115,65368,221],{"class":132},[115,65370,65371,65373,65375,65377],{"class":117,"line":380},[115,65372,1114],{"class":262},[115,65374,1117],{"class":132},[115,65376,1814],{"class":132},[115,65378,1817],{"class":202},[115,65380,65381,65383,65385],{"class":117,"line":487},[115,65382,1114],{"class":262},[115,65384,1117],{"class":132},[115,65386,11324],{"class":132},[115,65388,65389,65391,65393,65395],{"class":117,"line":2095},[115,65390,1114],{"class":262},[115,65392,1117],{"class":132},[115,65394,1838],{"class":132},[115,65396,1841],{"class":202},[115,65398,65399,65401,65403,65405],{"class":117,"line":2104},[115,65400,2001],{"class":262},[115,65402,3480],{"class":132},[115,65404,3483],{"class":132},[115,65406,1987],{"class":132},[115,65408,65409,65411,65413,65415],{"class":117,"line":2113},[115,65410,2001],{"class":262},[115,65412,3480],{"class":132},[115,65414,3919],{"class":132},[115,65416,1996],{"class":132},[16,65418,65419],{},"Post-deploy checks:",[106,65421,65423],{"className":108,"code":65422,"language":110,"meta":111,"style":111},"systemctl status gunicorn\nsystemctl status nginx\njournalctl -u gunicorn -n 100 --no-pager\ncurl -I https:\u002F\u002Fexample.com\n",[20,65424,65425,65433,65441,65455],{"__ignoreMap":111},[115,65426,65427,65429,65431],{"class":117,"line":118},[115,65428,1981],{"class":262},[115,65430,1984],{"class":132},[115,65432,1987],{"class":132},[115,65434,65435,65437,65439],{"class":117,"line":136},[115,65436,1981],{"class":262},[115,65438,1984],{"class":132},[115,65440,1996],{"class":132},[115,65442,65443,65445,65447,65449,65451,65453],{"class":117,"line":149},[115,65444,2785],{"class":262},[115,65446,2788],{"class":202},[115,65448,2791],{"class":132},[115,65450,2794],{"class":202},[115,65452,2797],{"class":202},[115,65454,2800],{"class":202},[115,65456,65457,65459,65461],{"class":117,"line":162},[115,65458,2764],{"class":262},[115,65460,2767],{"class":202},[115,65462,2770],{"class":132},[16,65464,1132],{},[63,65466,65467,65469,65471,65474,65477,65480],{},[66,65468,15882],{},[66,65470,1137],{},[66,65472,65473],{},"forms submit correctly",[66,65475,65476],{},"static files load",[66,65478,65479],{},"media files load if applicable",[66,65481,65482,65484],{},[20,65483,2719],{}," and CSRF behavior are correct under your real domain",[16,65486,65487,65488,65490,65491,65493],{},"Rollback note: this in-place ",[20,65489,24494],{}," method is simple, but not the safest rollback pattern. If a deploy breaks, you can check out the previous Git commit and restart Gunicorn, but that does not always fully reverse migrations or static asset changes. For lower-risk releases, move to versioned release directories and switch a ",[20,65492,13654],{}," symlink only after verification.",[52,65495,41766],{"id":41765},[16,65497,65498],{},"Once you repeat this process across multiple apps or environments, the manual steps become good candidates for reusable scripts or templates. The first parts worth automating are server bootstrap, env file placement, systemd unit creation, Nginx site creation, and the deploy sequence of pull, install, migrate, collectstatic, and restart. That reduces drift between servers and makes rollback more predictable.",[23099,65500],{},[11,65502,1321],{"id":1320},[16,65504,65505],{},"Gunicorn and Nginx are a common Django production stack because they split responsibilities cleanly. Gunicorn runs the Python application. Nginx handles client connections, static files, request buffering, and TLS termination.",[16,65507,65508],{},"systemd is better than a shell session because it keeps Gunicorn running after logout, restarts it on failure, and starts it automatically on reboot.",[16,65510,65511,65512,65514,65515,65517],{},"Environment-based secrets are safer than hardcoding values in ",[20,65513,10342],{}," or committing them to Git. A restricted file like ",[20,65516,14981],{}," is simple and works well on a single Droplet.",[16,65519,65520],{},"This setup is a good default when you want a straightforward DigitalOcean Django production deployment without introducing Docker or Kubernetes.",[23099,65522],{},[11,65524,10095],{"id":10094},[63,65526,65527,65533,65539,65545,65554,65560,65566],{},[66,65528,65529,65532],{},[1226,65530,65531],{},"No domain name yet:"," you can deploy using the Droplet IP, but HTTPS with Let’s Encrypt generally requires a domain name.",[66,65534,65535,65538],{},[1226,65536,65537],{},"SQLite in production:"," possible for temporary internal tools, but limited for concurrent writes, backups, and scaling.",[66,65540,65541,65544],{},[1226,65542,65543],{},"Database on same Droplet vs managed database:"," same-Droplet PostgreSQL is simpler and cheaper at small scale; managed PostgreSQL improves isolation, backups, and failover.",[66,65546,65547,65550,65551,65553],{},[1226,65548,65549],{},"User-uploaded media:"," Nginx can serve local media from ",[20,65552,13085],{},", but object storage becomes a better option as uploads grow.",[66,65555,65556,65559],{},[1226,65557,65558],{},"Migrations can break deploys:"," take backups before destructive changes, especially when removing columns or altering large tables.",[66,65561,65562,65565],{},[1226,65563,65564],{},"Single Droplet limits:"," once CPU, memory, or deploy risk becomes an issue, separate the database, move media off-box, and consider load balancing across app servers.",[66,65567,65568,65571],{},[1226,65569,65570],{},"HSTS caution:"," enable long HSTS values only after you confirm HTTPS works correctly for all required hostnames.",[23099,65573],{},[11,65575,1386],{"id":1385},[16,65577,65578,65579,65581],{},"Before deploying, review a ",[1395,65580,3000],{"href":2999}," to confirm hosts, secrets, cookies, TLS, and static configuration.",[16,65583,65584,65585,211],{},"If you want a deeper breakdown of the web stack, see ",[1395,65586,2986],{"href":2985},[16,65588,65589,65590,211],{},"If your project uses ASGI features, read ",[1395,65591,8039],{"href":8038},[16,65593,65594,65595,211],{},"For an alternative reverse proxy with simpler HTTPS management, see ",[1395,65596,8046],{"href":8045},[16,65598,65599,65600,65603],{},"If something fails during rollout, use a ",[1395,65601,65602],{"href":2999},"Django deployment troubleshooting checklist"," to work through 502s, missing static files, TLS problems, and startup errors.",[23099,65605],{},[11,65607,1420],{"id":1419},[52,65609,65611],{"id":65610},"can-i-deploy-django-on-the-smallest-digitalocean-droplet","Can I deploy Django on the smallest DigitalOcean Droplet?",[16,65613,65614],{},"Yes, for small apps or low-traffic internal tools. But memory is often the first limit, especially with PostgreSQL, Gunicorn workers, and background tasks on the same server. Monitor RAM and swap usage early.",[52,65616,65618],{"id":65617},"do-i-need-docker-to-deploy-django-on-digitalocean","Do I need Docker to deploy Django on DigitalOcean?",[16,65620,65621],{},"No. A standard Ubuntu Droplet with virtualenv, Gunicorn, Nginx, systemd, and PostgreSQL is a normal production setup. Docker helps with consistency, but it is not required.",[52,65623,65625],{"id":65624},"should-i-use-gunicorn-or-uvicorn-on-a-digitalocean-droplet","Should I use Gunicorn or Uvicorn on a DigitalOcean Droplet?",[16,65627,65628],{},"For a standard Django WSGI app, Gunicorn is the usual default. If your project depends on ASGI features such as WebSockets, use an ASGI-capable setup instead. For many Django apps, Gunicorn behind Nginx is the simplest stable choice.",[52,65630,65632],{"id":65631},"should-postgresql-run-on-the-same-droplet-as-django","Should PostgreSQL run on the same Droplet as Django?",[16,65634,65635],{},"It can, especially for smaller projects. That keeps setup simple. As the app grows, a managed database or separate database host usually gives better isolation, backups, and upgrade flexibility.",[52,65637,65639],{"id":65638},"how-do-i-update-the-app-without-noticeable-downtime","How do I update the app without noticeable downtime?",[16,65641,65642],{},"Keep Nginx running while you restart Gunicorn, and make deploys predictable: pull code, install dependencies, run migrations carefully, collect static files, then restart Gunicorn. For lower-risk releases, use versioned release directories and switch a symlink to the new release after verification.",[1485,65644,65645],{},"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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":111,"searchDepth":149,"depth":149,"links":65647},[65648,65649,65650,65651,65652,65653,65654,65655,65656,65657,65658,65661,65662,65663,65664],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":63554,"depth":136,"text":63555},{"id":63770,"depth":136,"text":63771},{"id":64322,"depth":136,"text":64323},{"id":64506,"depth":136,"text":64507},{"id":64719,"depth":136,"text":64720},{"id":64890,"depth":136,"text":64891},{"id":65231,"depth":136,"text":65232},{"id":65313,"depth":136,"text":65314,"children":65659},[65660],{"id":41765,"depth":149,"text":41766},{"id":1320,"depth":136,"text":1321},{"id":10094,"depth":136,"text":10095},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":65665},[65666,65667,65668,65669,65670],{"id":65610,"depth":149,"text":65611},{"id":65617,"depth":149,"text":65618},{"id":65624,"depth":149,"text":65625},{"id":65631,"depth":149,"text":65632},{"id":65638,"depth":149,"text":65639},"If you want to deploy Django on a DigitalOcean Droplet, you need more than python manage.py runserver.",{},"\u002Fdeploy-django-on-digitalocean",[2985,23041,16243],{"title":63442,"description":65671},[1557,65677,14954,2156],"digitalocean","deploy-django-on-digitalocean",[1557,65677,14954,2156],"D3VOFEhYFoO79CGmvOzgGdAe58SYVrixFAb100YNB8k",{"id":65682,"title":65683,"body":65684,"category":3088,"description":65690,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":1546,"meta":68047,"navigation":309,"path":68048,"priority":6330,"related":68049,"role":1553,"section":3098,"seo":68051,"stack":68052,"stem":68053,"tags":68054,"type":1561,"__hash__":68055},"articles\u002Fdockerize-django-production.md","How to Dockerize a Django App for Production",{"type":8,"value":65685,"toc":68020},[65686,65688,65691,65700,65703,65732,65734,65740,65766,65768,65772,65775,65788,65791,65803,65806,65810,65813,66316,66319,66348,66356,66359,66373,66375,66390,66393,66399,66402,66481,66485,66488,66715,66721,66727,66730,66813,66815,66832,66836,66839,67034,67037,67048,67051,67055,67058,67076,67079,67176,67184,67191,67193,67238,67241,67261,67265,67268,67271,67274,67277,67418,67421,67425,67428,67451,67454,67481,67484,67758,67761,67847,67850,67875,67878,67880,67883,67885,67888,67891,67894,67897,67900,67908,67910,67953,67955,67960,67965,67970,67975,67977,67984,67987,67991,67996,68000,68003,68007,68010,68014,68017],[11,65687,14],{"id":13},[16,65689,65690],{},"A Dockerized Django app that works in development is not automatically safe for production.",[16,65692,65693,65694,65696,65697,65699],{},"Typical development setups run ",[20,65695,11657],{},", mount local source code into the container, keep ",[20,65698,24957],{},", and rely on ad hoc environment variables. That is fine for local work, but it breaks down in production where you need predictable builds, secret handling, static file strategy, proper process startup, controlled migrations, logs to stdout\u002Fstderr, and a rollback path.",[16,65701,65702],{},"A production-ready Django Docker container should handle:",[63,65704,65705,65708,65711,65714,65717,65720,65723,65726,65729],{},[66,65706,65707],{},"deterministic image builds",[66,65709,65710],{},"runtime configuration through environment variables",[66,65712,65713],{},"non-root execution",[66,65715,65716],{},"Gunicorn or Uvicorn process startup",[66,65718,65719],{},"static file collection",[66,65721,65722],{},"health checks that verify request handling",[66,65724,65725],{},"controlled database migrations",[66,65727,65728],{},"external PostgreSQL, Redis, and media storage",[66,65730,65731],{},"proxy-aware HTTPS settings",[11,65733,30],{"id":29},[16,65735,65736,65737,11703],{},"The safest way to ",[1226,65738,65739],{},"dockerize a Django app for production",[1173,65741,65742,65745,65748,65751,65754,65757,65760,65763],{},[66,65743,65744],{},"build a minimal image from a slim Python base",[66,65746,65747],{},"install dependencies predictably from a pinned requirements file",[66,65749,65750],{},"run Django with Gunicorn",[66,65752,65753],{},"inject secrets at runtime, not at build time",[66,65755,65756],{},"collect static files during build or as a controlled release step",[66,65758,65759],{},"keep the database, Redis, and uploaded media outside the container",[66,65761,65762],{},"put Nginx, Caddy, or a load balancer in front when you need TLS termination, buffering, and static\u002Fmedia routing",[66,65764,65765],{},"tag every image version so you can roll back quickly",[11,65767,43],{"id":42},[11,65769,65771],{"id":65770},"_1-choose-a-production-container-architecture","1. Choose a production container architecture",[16,65773,65774],{},"What should go inside the Django container:",[63,65776,65777,65780,65783,65785],{},[66,65778,65779],{},"Django application code",[66,65781,65782],{},"Python dependencies",[66,65784,1946],{},[66,65786,65787],{},"optional startup script",[16,65789,65790],{},"What should stay outside:",[63,65792,65793,65795,65797,65800],{},[66,65794,1773],{},[66,65796,1955],{},[66,65798,65799],{},"uploaded media storage",[66,65801,65802],{},"TLS termination and reverse proxy layer",[16,65804,65805],{},"If your app only serves web requests, one web container may be enough. If you use Celery or scheduled jobs, run separate worker and beat containers from the same image with different commands.",[11,65807,65809],{"id":65808},"_2-prepare-django-settings-for-containerized-production","2. Prepare Django settings for containerized production",[16,65811,65812],{},"Use environment variables for all production-specific values.",[106,65814,65816],{"className":2369,"code":65815,"language":1114,"meta":111,"style":111},"# settings.py\nimport os\nfrom django.core.exceptions import ImproperlyConfigured\n\ndef env(name, default=None):\n    value = os.getenv(name, default)\n    if value is None:\n        raise ImproperlyConfigured(f\"Missing required environment variable: {name}\")\n    return value\n\nDEBUG = False\n\nSECRET_KEY = env(\"DJANGO_SECRET_KEY\")\nALLOWED_HOSTS = [h.strip() for h in env(\"DJANGO_ALLOWED_HOSTS\").split(\",\") if h.strip()]\n\ncsrf_origins = os.getenv(\"DJANGO_CSRF_TRUSTED_ORIGINS\", \"\")\nCSRF_TRUSTED_ORIGINS = [x.strip() for x in csrf_origins.split(\",\") if x.strip()]\n\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\nUSE_X_FORWARDED_HOST = True\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.postgresql\",\n        \"NAME\": env(\"POSTGRES_DB\"),\n        \"USER\": env(\"POSTGRES_USER\"),\n        \"PASSWORD\": env(\"POSTGRES_PASSWORD\"),\n        \"HOST\": env(\"POSTGRES_HOST\"),\n        \"PORT\": os.getenv(\"POSTGRES_PORT\", \"5432\"),\n        \"CONN_MAX_AGE\": 60,\n    }\n}\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = \"\u002Fapp\u002Fstaticfiles\"\n\nMEDIA_URL = \"\u002Fmedia\u002F\"\nMEDIA_ROOT = \"\u002Fapp\u002Fmedia\"\n\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"handlers\": {\n        \"console\": {\"class\": \"logging.StreamHandler\"},\n    },\n    \"root\": {\n        \"handlers\": [\"console\"],\n        \"level\": os.getenv(\"DJANGO_LOG_LEVEL\", \"INFO\"),\n    },\n}\n",[20,65817,65818,65822,65828,65840,65844,65859,65869,65884,65909,65916,65920,65928,65932,65945,65973,65977,65994,66022,66026,66042,66050,66058,66066,66074,66078,66086,66092,66102,66114,66125,66136,66147,66163,66173,66177,66181,66185,66193,66201,66205,66213,66221,66225,66233,66243,66253,66259,66274,66278,66284,66294,66308,66312],{"__ignoreMap":111},[115,65819,65820],{"class":117,"line":118},[115,65821,42173],{"class":3861},[115,65823,65824,65826],{"class":117,"line":136},[115,65825,5613],{"class":121},[115,65827,5616],{"class":125},[115,65829,65830,65832,65835,65837],{"class":117,"line":149},[115,65831,5621],{"class":121},[115,65833,65834],{"class":125}," django.core.exceptions ",[115,65836,5613],{"class":121},[115,65838,65839],{"class":125}," ImproperlyConfigured\n",[115,65841,65842],{"class":117,"line":162},[115,65843,310],{"emptyLinePlaceholder":309},[115,65845,65846,65848,65850,65852,65854,65857],{"class":117,"line":175},[115,65847,8808],{"class":121},[115,65849,5263],{"class":262},[115,65851,40516],{"class":125},[115,65853,129],{"class":121},[115,65855,65856],{"class":202},"None",[115,65858,37156],{"class":125},[115,65860,65861,65864,65866],{"class":117,"line":350},[115,65862,65863],{"class":125},"    value ",[115,65865,129],{"class":121},[115,65867,65868],{"class":125}," os.getenv(name, default)\n",[115,65870,65871,65873,65876,65879,65882],{"class":117,"line":365},[115,65872,40975],{"class":121},[115,65874,65875],{"class":125}," value ",[115,65877,65878],{"class":121},"is",[115,65880,65881],{"class":202}," None",[115,65883,2498],{"class":125},[115,65885,65886,65889,65892,65894,65897,65900,65902,65905,65907],{"class":117,"line":380},[115,65887,65888],{"class":121},"        raise",[115,65890,65891],{"class":125}," ImproperlyConfigured(",[115,65893,36099],{"class":121},[115,65895,65896],{"class":132},"\"Missing required environment variable: ",[115,65898,65899],{"class":202},"{",[115,65901,20820],{"class":125},[115,65903,65904],{"class":202},"}",[115,65906,331],{"class":132},[115,65908,2394],{"class":125},[115,65910,65911,65913],{"class":117,"line":487},[115,65912,3822],{"class":121},[115,65914,65915],{"class":125}," value\n",[115,65917,65918],{"class":117,"line":2095},[115,65919,310],{"emptyLinePlaceholder":309},[115,65921,65922,65924,65926],{"class":117,"line":2104},[115,65923,7350],{"class":202},[115,65925,2380],{"class":121},[115,65927,7355],{"class":202},[115,65929,65930],{"class":117,"line":2113},[115,65931,310],{"emptyLinePlaceholder":309},[115,65933,65934,65936,65938,65941,65943],{"class":117,"line":2122},[115,65935,2713],{"class":202},[115,65937,2380],{"class":121},[115,65939,65940],{"class":125}," env(",[115,65942,12063],{"class":132},[115,65944,2394],{"class":125},[115,65946,65947,65949,65951,65953,65955,65957,65959,65961,65963,65965,65967,65969,65971],{"class":117,"line":2131},[115,65948,2719],{"class":202},[115,65950,2380],{"class":121},[115,65952,18253],{"class":125},[115,65954,18256],{"class":121},[115,65956,18259],{"class":125},[115,65958,18262],{"class":121},[115,65960,65940],{"class":125},[115,65962,25202],{"class":132},[115,65964,18275],{"class":125},[115,65966,18278],{"class":132},[115,65968,18281],{"class":125},[115,65970,10833],{"class":121},[115,65972,18286],{"class":125},[115,65974,65975],{"class":117,"line":2136},[115,65976,310],{"emptyLinePlaceholder":309},[115,65978,65979,65982,65984,65986,65988,65990,65992],{"class":117,"line":2142},[115,65980,65981],{"class":125},"csrf_origins ",[115,65983,129],{"class":121},[115,65985,35866],{"class":125},[115,65987,25237],{"class":132},[115,65989,1153],{"class":125},[115,65991,18272],{"class":132},[115,65993,2394],{"class":125},[115,65995,65996,65998,66000,66003,66005,66008,66010,66013,66015,66017,66019],{"class":117,"line":2273},[115,65997,2725],{"class":202},[115,65999,2380],{"class":121},[115,66001,66002],{"class":125}," [x.strip() ",[115,66004,18256],{"class":121},[115,66006,66007],{"class":125}," x ",[115,66009,18262],{"class":121},[115,66011,66012],{"class":125}," csrf_origins.split(",[115,66014,18278],{"class":132},[115,66016,18281],{"class":125},[115,66018,10833],{"class":121},[115,66020,66021],{"class":125}," x.strip()]\n",[115,66023,66024],{"class":117,"line":2282},[115,66025,310],{"emptyLinePlaceholder":309},[115,66027,66028,66030,66032,66034,66036,66038,66040],{"class":117,"line":2291},[115,66029,2377],{"class":202},[115,66031,2380],{"class":121},[115,66033,2383],{"class":125},[115,66035,2386],{"class":132},[115,66037,1153],{"class":125},[115,66039,2391],{"class":132},[115,66041,2394],{"class":125},[115,66043,66044,66046,66048],{"class":117,"line":2299},[115,66045,12021],{"class":202},[115,66047,2380],{"class":121},[115,66049,2412],{"class":202},[115,66051,66052,66054,66056],{"class":117,"line":2307},[115,66053,2407],{"class":202},[115,66055,2380],{"class":121},[115,66057,2412],{"class":202},[115,66059,66060,66062,66064],{"class":117,"line":2315},[115,66061,2417],{"class":202},[115,66063,2380],{"class":121},[115,66065,2412],{"class":202},[115,66067,66068,66070,66072],{"class":117,"line":2320},[115,66069,2426],{"class":202},[115,66071,2380],{"class":121},[115,66073,2412],{"class":202},[115,66075,66076],{"class":117,"line":7083},[115,66077,310],{"emptyLinePlaceholder":309},[115,66079,66080,66082,66084],{"class":117,"line":7090},[115,66081,10632],{"class":202},[115,66083,2380],{"class":121},[115,66085,2166],{"class":125},[115,66087,66088,66090],{"class":117,"line":7097},[115,66089,10664],{"class":132},[115,66091,3374],{"class":125},[115,66093,66094,66096,66098,66100],{"class":117,"line":7108},[115,66095,10671],{"class":132},[115,66097,2513],{"class":125},[115,66099,10676],{"class":132},[115,66101,3354],{"class":125},[115,66103,66104,66106,66109,66112],{"class":117,"line":7113},[115,66105,10683],{"class":132},[115,66107,66108],{"class":125},": env(",[115,66110,66111],{"class":132},"\"POSTGRES_DB\"",[115,66113,10746],{"class":125},[115,66115,66116,66118,66120,66123],{"class":117,"line":16535},[115,66117,10696],{"class":132},[115,66119,66108],{"class":125},[115,66121,66122],{"class":132},"\"POSTGRES_USER\"",[115,66124,10746],{"class":125},[115,66126,66127,66129,66131,66134],{"class":117,"line":16544},[115,66128,10708],{"class":132},[115,66130,66108],{"class":125},[115,66132,66133],{"class":132},"\"POSTGRES_PASSWORD\"",[115,66135,10746],{"class":125},[115,66137,66138,66140,66142,66145],{"class":117,"line":16549},[115,66139,10720],{"class":132},[115,66141,66108],{"class":125},[115,66143,66144],{"class":132},"\"POSTGRES_HOST\"",[115,66146,10746],{"class":125},[115,66148,66149,66151,66154,66157,66159,66161],{"class":117,"line":16555},[115,66150,10732],{"class":132},[115,66152,66153],{"class":125},": os.getenv(",[115,66155,66156],{"class":132},"\"POSTGRES_PORT\"",[115,66158,1153],{"class":125},[115,66160,10743],{"class":132},[115,66162,10746],{"class":125},[115,66164,66165,66167,66169,66171],{"class":117,"line":16564},[115,66166,10751],{"class":132},[115,66168,2513],{"class":125},[115,66170,11461],{"class":202},[115,66172,3354],{"class":125},[115,66174,66175],{"class":117,"line":16573},[115,66176,2233],{"class":125},[115,66178,66179],{"class":117,"line":16582},[115,66180,2323],{"class":125},[115,66182,66183],{"class":117,"line":16587},[115,66184,310],{"emptyLinePlaceholder":309},[115,66186,66187,66189,66191],{"class":117,"line":16596},[115,66188,11908],{"class":202},[115,66190,2380],{"class":121},[115,66192,11913],{"class":132},[115,66194,66195,66197,66199],{"class":117,"line":16609},[115,66196,11918],{"class":202},[115,66198,2380],{"class":121},[115,66200,25317],{"class":132},[115,66202,66203],{"class":117,"line":16614},[115,66204,310],{"emptyLinePlaceholder":309},[115,66206,66207,66209,66211],{"class":117,"line":16624},[115,66208,15204],{"class":202},[115,66210,2380],{"class":121},[115,66212,15209],{"class":132},[115,66214,66215,66217,66219],{"class":117,"line":16632},[115,66216,15214],{"class":202},[115,66218,2380],{"class":121},[115,66220,25338],{"class":132},[115,66222,66223],{"class":117,"line":16640},[115,66224,310],{"emptyLinePlaceholder":309},[115,66226,66227,66229,66231],{"class":117,"line":16646},[115,66228,3337],{"class":202},[115,66230,2380],{"class":121},[115,66232,2166],{"class":125},[115,66234,66235,66237,66239,66241],{"class":117,"line":16651},[115,66236,3346],{"class":132},[115,66238,2513],{"class":125},[115,66240,3351],{"class":202},[115,66242,3354],{"class":125},[115,66244,66245,66247,66249,66251],{"class":117,"line":16656},[115,66246,3359],{"class":132},[115,66248,2513],{"class":125},[115,66250,3364],{"class":202},[115,66252,3354],{"class":125},[115,66254,66255,66257],{"class":117,"line":16666},[115,66256,3371],{"class":132},[115,66258,3374],{"class":125},[115,66260,66261,66263,66265,66268,66270,66272],{"class":117,"line":16674},[115,66262,3379],{"class":132},[115,66264,9131],{"class":125},[115,66266,66267],{"class":132},"\"class\"",[115,66269,2513],{"class":125},[115,66271,3391],{"class":132},[115,66273,9142],{"class":125},[115,66275,66276],{"class":117,"line":16687},[115,66277,3403],{"class":125},[115,66279,66280,66282],{"class":117,"line":16692},[115,66281,36132],{"class":132},[115,66283,3374],{"class":125},[115,66285,66286,66288,66290,66292],{"class":117,"line":16697},[115,66287,36139],{"class":132},[115,66289,2541],{"class":125},[115,66291,3427],{"class":132},[115,66293,3430],{"class":125},[115,66295,66296,66298,66300,66302,66304,66306],{"class":117,"line":16702},[115,66297,36164],{"class":132},[115,66299,66153],{"class":125},[115,66301,35893],{"class":132},[115,66303,1153],{"class":125},[115,66305,35898],{"class":132},[115,66307,10746],{"class":125},[115,66309,66310],{"class":117,"line":16711},[115,66311,3403],{"class":125},[115,66313,66314],{"class":117,"line":16719},[115,66315,2323],{"class":125},[16,66317,66318],{},"If your site is HTTPS-only and you control the full domain setup, add HSTS as well:",[106,66320,66322],{"className":2369,"code":66321,"language":1114,"meta":111,"style":111},"SECURE_HSTS_SECONDS = 31536000\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\n",[20,66323,66324,66332,66340],{"__ignoreMap":111},[115,66325,66326,66328,66330],{"class":117,"line":118},[115,66327,7440],{"class":202},[115,66329,2380],{"class":121},[115,66331,11991],{"class":202},[115,66333,66334,66336,66338],{"class":117,"line":136},[115,66335,7464],{"class":202},[115,66337,2380],{"class":121},[115,66339,2412],{"class":202},[115,66341,66342,66344,66346],{"class":117,"line":149},[115,66343,12004],{"class":202},[115,66345,2380],{"class":121},[115,66347,2412],{"class":202},[16,66349,43602,66350,66352,66353,66355],{},[20,66351,2725],{},", use full origins with scheme, such as ",[20,66354,2963],{},". Do not use plain hostnames in production.",[16,66357,66358],{},"Add a startup validation check in your release workflow:",[106,66360,66361],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,66362,66363],{"__ignoreMap":111},[115,66364,66365,66367,66369,66371],{"class":117,"line":118},[115,66366,1114],{"class":262},[115,66368,1117],{"class":132},[115,66370,1814],{"class":132},[115,66372,1817],{"class":202},[16,66374,8572],{},[63,66376,66377,66381,66384,66387],{},[66,66378,4183,66379],{},[20,66380,2707],{},[66,66382,66383],{},"confirm required variables are enforced",[66,66385,66386],{},"confirm logs appear in container output",[66,66388,66389],{},"confirm HTTPS settings match your proxy setup",[16,66391,66392],{},"Rollback note: if a settings change breaks startup, redeploy the previous image tag with the previous environment file.",[11,66394,66396,66397],{"id":66395},"_3-add-a-dockerignore","3. Add a ",[20,66398,18819],{},[16,66400,66401],{},"Do not send local secrets and build junk into the image context.",[106,66403,66407],{"className":66404,"code":66405,"language":66406,"meta":111,"style":111},"language-dockerignore shiki shiki-themes github-light github-dark",".git\n.gitignore\n.env\n.env.*\nvenv\n.venv\n__pycache__\n*.pyc\n.pytest_cache\n.mypy_cache\nnode_modules\ndist\nbuild\nmedia\nstaticfiles\n","dockerignore",[20,66408,66409,66413,66418,66422,66427,66432,66437,66442,66446,66451,66456,66461,66466,66471,66476],{"__ignoreMap":111},[115,66410,66411],{"class":117,"line":118},[115,66412,18831],{},[115,66414,66415],{"class":117,"line":136},[115,66416,66417],{},".gitignore\n",[115,66419,66420],{"class":117,"line":149},[115,66421,2526],{},[115,66423,66424],{"class":117,"line":162},[115,66425,66426],{},".env.*\n",[115,66428,66429],{"class":117,"line":175},[115,66430,66431],{},"venv\n",[115,66433,66434],{"class":117,"line":350},[115,66435,66436],{},".venv\n",[115,66438,66439],{"class":117,"line":365},[115,66440,66441],{},"__pycache__\n",[115,66443,66444],{"class":117,"line":380},[115,66445,18841],{},[115,66447,66448],{"class":117,"line":487},[115,66449,66450],{},".pytest_cache\n",[115,66452,66453],{"class":117,"line":2095},[115,66454,66455],{},".mypy_cache\n",[115,66457,66458],{"class":117,"line":2104},[115,66459,66460],{},"node_modules\n",[115,66462,66463],{"class":117,"line":2113},[115,66464,66465],{},"dist\n",[115,66467,66468],{"class":117,"line":2122},[115,66469,66470],{},"build\n",[115,66472,66473],{"class":117,"line":2131},[115,66474,66475],{},"media\n",[115,66477,66478],{"class":117,"line":2136},[115,66479,66480],{},"staticfiles\n",[11,66482,66484],{"id":66483},"_4-write-a-production-django-dockerfile","4. Write a production Django Dockerfile",[16,66486,66487],{},"This example uses a slim Python base, installs only required system packages, creates a non-root user, and runs Gunicorn.",[106,66489,66491],{"className":16832,"code":66490,"language":16834,"meta":111,"style":111},"FROM python:3.12-slim\n\nENV PYTHONDONTWRITEBYTECODE=1 \\\n    PYTHONUNBUFFERED=1\n\nWORKDIR \u002Fapp\n\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    libpq5 \\\n    netcat-openbsd \\\n    && rm -rf \u002Fvar\u002Flib\u002Fapt\u002Flists\u002F*\n\nCOPY requirements.txt .\nRUN pip install --no-cache-dir --upgrade pip \\\n    && pip install --no-cache-dir -r requirements.txt\n\nRUN addgroup --system django && adduser --system --ingroup django django\n\nCOPY . \u002Fapp\n\nRUN chmod +x \u002Fapp\u002Fentrypoint.sh \\\n    && mkdir -p \u002Fapp\u002Fstaticfiles \u002Fapp\u002Fmedia \\\n    && chown -R django:django \u002Fapp\n\nUSER django\n\nEXPOSE 8000\n\nHEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \\\n  CMD python -c \"import urllib.request; urllib.request.urlopen('http:\u002F\u002F127.0.0.1:8000\u002Fhealthz', timeout=3)\" || exit 1\n\nENTRYPOINT [\"\u002Fapp\u002Fentrypoint.sh\"]\nCMD [\"gunicorn\", \"--bind\", \"0.0.0.0:8000\", \"--workers\", \"3\", \"--timeout\", \"60\", \"projectname.wsgi:application\"]\n",[20,66492,66493,66499,66503,66510,66515,66519,66525,66529,66535,66540,66545,66549,66553,66559,66566,66571,66575,66582,66586,66593,66597,66604,66609,66614,66618,66625,66629,66636,66640,66647,66660,66664,66676],{"__ignoreMap":111},[115,66494,66495,66497],{"class":117,"line":118},[115,66496,11089],{"class":121},[115,66498,16843],{"class":125},[115,66500,66501],{"class":117,"line":136},[115,66502,310],{"emptyLinePlaceholder":309},[115,66504,66505,66507],{"class":117,"line":149},[115,66506,16852],{"class":121},[115,66508,66509],{"class":125}," PYTHONDONTWRITEBYTECODE=1 \\\n",[115,66511,66512],{"class":117,"line":162},[115,66513,66514],{"class":125},"    PYTHONUNBUFFERED=1\n",[115,66516,66517],{"class":117,"line":175},[115,66518,310],{"emptyLinePlaceholder":309},[115,66520,66521,66523],{"class":117,"line":350},[115,66522,16885],{"class":121},[115,66524,16888],{"class":125},[115,66526,66527],{"class":117,"line":365},[115,66528,310],{"emptyLinePlaceholder":309},[115,66530,66531,66533],{"class":117,"line":380},[115,66532,16905],{"class":121},[115,66534,25548],{"class":125},[115,66536,66537],{"class":117,"line":487},[115,66538,66539],{"class":125},"    libpq5 \\\n",[115,66541,66542],{"class":117,"line":2095},[115,66543,66544],{"class":125},"    netcat-openbsd \\\n",[115,66546,66547],{"class":117,"line":2104},[115,66548,25558],{"class":125},[115,66550,66551],{"class":117,"line":2113},[115,66552,310],{"emptyLinePlaceholder":309},[115,66554,66555,66557],{"class":117,"line":2122},[115,66556,16897],{"class":121},[115,66558,16900],{"class":125},[115,66560,66561,66563],{"class":117,"line":2131},[115,66562,16905],{"class":121},[115,66564,66565],{"class":125}," pip install --no-cache-dir --upgrade pip \\\n",[115,66567,66568],{"class":117,"line":2136},[115,66569,66570],{"class":125},"    && pip install --no-cache-dir -r requirements.txt\n",[115,66572,66573],{"class":117,"line":2142},[115,66574,310],{"emptyLinePlaceholder":309},[115,66576,66577,66579],{"class":117,"line":2273},[115,66578,16905],{"class":121},[115,66580,66581],{"class":125}," addgroup --system django && adduser --system --ingroup django django\n",[115,66583,66584],{"class":117,"line":2282},[115,66585,310],{"emptyLinePlaceholder":309},[115,66587,66588,66590],{"class":117,"line":2291},[115,66589,16897],{"class":121},[115,66591,66592],{"class":125}," . \u002Fapp\n",[115,66594,66595],{"class":117,"line":2299},[115,66596,310],{"emptyLinePlaceholder":309},[115,66598,66599,66601],{"class":117,"line":2307},[115,66600,16905],{"class":121},[115,66602,66603],{"class":125}," chmod +x \u002Fapp\u002Fentrypoint.sh \\\n",[115,66605,66606],{"class":117,"line":2315},[115,66607,66608],{"class":125},"    && mkdir -p \u002Fapp\u002Fstaticfiles \u002Fapp\u002Fmedia \\\n",[115,66610,66611],{"class":117,"line":2320},[115,66612,66613],{"class":125},"    && chown -R django:django \u002Fapp\n",[115,66615,66616],{"class":117,"line":7083},[115,66617,310],{"emptyLinePlaceholder":309},[115,66619,66620,66622],{"class":117,"line":7090},[115,66621,25600],{"class":121},[115,66623,66624],{"class":125}," django\n",[115,66626,66627],{"class":117,"line":7097},[115,66628,310],{"emptyLinePlaceholder":309},[115,66630,66631,66634],{"class":117,"line":7108},[115,66632,66633],{"class":121},"EXPOSE",[115,66635,23864],{"class":125},[115,66637,66638],{"class":117,"line":7113},[115,66639,310],{"emptyLinePlaceholder":309},[115,66641,66642,66644],{"class":117,"line":16535},[115,66643,54787],{"class":121},[115,66645,66646],{"class":125}," --interval=30s --timeout=5s --start-period=30s --retries=3 \\\n",[115,66648,66649,66651,66654,66657],{"class":117,"line":16544},[115,66650,54795],{"class":121},[115,66652,66653],{"class":125}," python -c ",[115,66655,66656],{"class":132},"\"import urllib.request; urllib.request.urlopen('http:\u002F\u002F127.0.0.1:8000\u002Fhealthz', timeout=3)\"",[115,66658,66659],{"class":125}," || exit 1\n",[115,66661,66662],{"class":117,"line":16549},[115,66663,310],{"emptyLinePlaceholder":309},[115,66665,66666,66669,66671,66674],{"class":117,"line":16555},[115,66667,66668],{"class":121},"ENTRYPOINT",[115,66670,7493],{"class":125},[115,66672,66673],{"class":132},"\"\u002Fapp\u002Fentrypoint.sh\"",[115,66675,2552],{"class":125},[115,66677,66678,66680,66682,66684,66686,66688,66690,66692,66694,66696,66698,66700,66702,66704,66706,66708,66710,66713],{"class":117,"line":16564},[115,66679,16939],{"class":121},[115,66681,7493],{"class":125},[115,66683,16944],{"class":132},[115,66685,1153],{"class":125},[115,66687,16949],{"class":132},[115,66689,1153],{"class":125},[115,66691,16954],{"class":132},[115,66693,1153],{"class":125},[115,66695,16959],{"class":132},[115,66697,1153],{"class":125},[115,66699,25637],{"class":132},[115,66701,1153],{"class":125},[115,66703,25642],{"class":132},[115,66705,1153],{"class":125},[115,66707,10767],{"class":132},[115,66709,1153],{"class":125},[115,66711,66712],{"class":132},"\"projectname.wsgi:application\"",[115,66714,2552],{"class":125},[16,66716,12628,66717,66720],{},[20,66718,66719],{},"projectname.wsgi:application"," with your real WSGI path.",[16,66722,66723,66724,66726],{},"The Docker health check should call a real HTTP endpoint, not ",[20,66725,41772],{},". A settings check can pass while Gunicorn is not serving requests.",[16,66728,66729],{},"Add a simple health endpoint in Django, for example:",[106,66731,66733],{"className":2369,"code":66732,"language":1114,"meta":111,"style":111},"# urls.py\nfrom django.http import HttpResponse\nfrom django.urls import path\n\ndef healthz(request):\n    return HttpResponse(\"ok\", content_type=\"text\u002Fplain\")\n\nurlpatterns = [\n    path(\"healthz\", healthz),\n]\n",[20,66734,66735,66739,66749,66759,66763,66771,66789,66793,66801,66809],{"__ignoreMap":111},[115,66736,66737],{"class":117,"line":118},[115,66738,18587],{"class":3861},[115,66740,66741,66743,66745,66747],{"class":117,"line":136},[115,66742,5621],{"class":121},[115,66744,17240],{"class":125},[115,66746,5613],{"class":121},[115,66748,17245],{"class":125},[115,66750,66751,66753,66755,66757],{"class":117,"line":149},[115,66752,5621],{"class":121},[115,66754,17252],{"class":125},[115,66756,5613],{"class":121},[115,66758,17257],{"class":125},[115,66760,66761],{"class":117,"line":162},[115,66762,310],{"emptyLinePlaceholder":309},[115,66764,66765,66767,66769],{"class":117,"line":175},[115,66766,8808],{"class":121},[115,66768,17268],{"class":262},[115,66770,17271],{"class":125},[115,66772,66773,66775,66777,66779,66781,66783,66785,66787],{"class":117,"line":350},[115,66774,3822],{"class":121},[115,66776,17278],{"class":125},[115,66778,17281],{"class":132},[115,66780,1153],{"class":125},[115,66782,17286],{"class":5680},[115,66784,129],{"class":121},[115,66786,17291],{"class":132},[115,66788,2394],{"class":125},[115,66790,66791],{"class":117,"line":365},[115,66792,310],{"emptyLinePlaceholder":309},[115,66794,66795,66797,66799],{"class":117,"line":380},[115,66796,17302],{"class":125},[115,66798,129],{"class":121},[115,66800,3540],{"class":125},[115,66802,66803,66805,66807],{"class":117,"line":487},[115,66804,17311],{"class":125},[115,66806,17314],{"class":132},[115,66808,17317],{"class":125},[115,66810,66811],{"class":117,"line":2095},[115,66812,2552],{"class":125},[16,66814,8508],{},[63,66816,66817,66823],{},[66,66818,66819,66822],{},[20,66820,66821],{},"libpq5"," is needed at runtime for PostgreSQL client libraries in many setups.",[66,66824,66825,66826,3146,66829,66831],{},"If your dependencies need compilation, you may also need build packages like ",[20,66827,66828],{},"build-essential",[20,66830,29104],{},". In that case, prefer a multi-stage build.",[11,66833,66835],{"id":66834},"_5-add-an-entrypoint-for-startup-tasks","5. Add an entrypoint for startup tasks",[16,66837,66838],{},"Use the entrypoint for controlled startup behavior, not for hidden deployment logic.",[106,66840,66844],{"className":66841,"code":66842,"language":66843,"meta":111,"style":111},"language-sh shiki shiki-themes github-light github-dark","#!\u002Fbin\u002Fsh\nset -e\n\nif [ -n \"$POSTGRES_HOST\" ]; then\n  echo \"Waiting for PostgreSQL at $POSTGRES_HOST:${POSTGRES_PORT:-5432}...\"\n  while ! nc -z \"$POSTGRES_HOST\" \"${POSTGRES_PORT:-5432}\"; do\n    sleep 1\n  done\nfi\n\npython manage.py collectstatic --noinput\n\nif [ \"$RUN_MIGRATIONS\" = \"1\" ]; then\n  python manage.py migrate --noinput\nfi\n\nexec \"$@\"\n","sh",[20,66845,66846,66851,66858,66862,66885,66909,66946,66953,66958,66963,66967,66977,66981,67003,67014,67018,67022],{"__ignoreMap":111},[115,66847,66848],{"class":117,"line":118},[115,66849,66850],{"class":3861},"#!\u002Fbin\u002Fsh\n",[115,66852,66853,66855],{"class":117,"line":136},[115,66854,203],{"class":202},[115,66856,66857],{"class":202}," -e\n",[115,66859,66860],{"class":117,"line":149},[115,66861,310],{"emptyLinePlaceholder":309},[115,66863,66864,66866,66869,66872,66874,66877,66879,66882],{"class":117,"line":162},[115,66865,10833],{"class":121},[115,66867,66868],{"class":125}," [ ",[115,66870,66871],{"class":121},"-n",[115,66873,325],{"class":132},[115,66875,66876],{"class":125},"$POSTGRES_HOST",[115,66878,331],{"class":132},[115,66880,66881],{"class":125}," ]; ",[115,66883,66884],{"class":121},"then\n",[115,66886,66887,66890,66893,66895,66898,66901,66904,66906],{"class":117,"line":175},[115,66888,66889],{"class":202},"  echo",[115,66891,66892],{"class":132}," \"Waiting for PostgreSQL at ",[115,66894,66876],{"class":125},[115,66896,66897],{"class":132},":${",[115,66899,66900],{"class":125},"POSTGRES_PORT",[115,66902,66903],{"class":121},":-",[115,66905,58844],{"class":125},[115,66907,66908],{"class":132},"}...\"\n",[115,66910,66911,66914,66917,66920,66923,66925,66927,66929,66931,66933,66935,66937,66940,66943],{"class":117,"line":350},[115,66912,66913],{"class":121},"  while",[115,66915,66916],{"class":121}," !",[115,66918,66919],{"class":262}," nc",[115,66921,66922],{"class":202}," -z",[115,66924,325],{"class":132},[115,66926,66876],{"class":125},[115,66928,331],{"class":132},[115,66930,19856],{"class":132},[115,66932,66900],{"class":125},[115,66934,66903],{"class":121},[115,66936,58844],{"class":125},[115,66938,66939],{"class":132},"}\"",[115,66941,66942],{"class":125},"; ",[115,66944,66945],{"class":121},"do\n",[115,66947,66948,66951],{"class":117,"line":365},[115,66949,66950],{"class":262},"    sleep",[115,66952,8995],{"class":202},[115,66954,66955],{"class":117,"line":380},[115,66956,66957],{"class":121},"  done\n",[115,66959,66960],{"class":117,"line":487},[115,66961,66962],{"class":121},"fi\n",[115,66964,66965],{"class":117,"line":2095},[115,66966,310],{"emptyLinePlaceholder":309},[115,66968,66969,66971,66973,66975],{"class":117,"line":2104},[115,66970,1114],{"class":262},[115,66972,1117],{"class":132},[115,66974,1838],{"class":132},[115,66976,1841],{"class":202},[115,66978,66979],{"class":117,"line":2113},[115,66980,310],{"emptyLinePlaceholder":309},[115,66982,66983,66985,66987,66989,66992,66994,66996,66999,67001],{"class":117,"line":2122},[115,66984,10833],{"class":121},[115,66986,66868],{"class":125},[115,66988,331],{"class":132},[115,66990,66991],{"class":125},"$RUN_MIGRATIONS",[115,66993,331],{"class":132},[115,66995,2380],{"class":121},[115,66997,66998],{"class":132}," \"1\"",[115,67000,66881],{"class":125},[115,67002,66884],{"class":121},[115,67004,67005,67008,67010,67012],{"class":117,"line":2131},[115,67006,67007],{"class":262},"  python",[115,67009,1117],{"class":132},[115,67011,1826],{"class":132},[115,67013,1841],{"class":202},[115,67015,67016],{"class":117,"line":2136},[115,67017,66962],{"class":121},[115,67019,67020],{"class":117,"line":2142},[115,67021,310],{"emptyLinePlaceholder":309},[115,67023,67024,67027,67029,67032],{"class":117,"line":2273},[115,67025,67026],{"class":202},"exec",[115,67028,325],{"class":132},[115,67030,67031],{"class":202},"$@",[115,67033,391],{"class":132},[16,67035,67036],{},"This gives you explicit control:",[63,67038,67039,67045],{},[66,67040,3192,67041,67044],{},[20,67042,67043],{},"RUN_MIGRATIONS=1"," only for a release where migrations should run",[66,67046,67047],{},"leave it unset for normal restarts",[16,67049,67050],{},"Avoid automatic migrations on every container start in multi-instance deployments. Two containers starting at once can turn startup into a release step you no longer control.",[11,67052,67054],{"id":67053},"_6-build-and-run-the-container-locally-in-production-mode","6. Build and run the container locally in production mode",[16,67056,67057],{},"Build the image:",[106,67059,67061],{"className":108,"code":67060,"language":110,"meta":111,"style":111},"docker build -t myapp:1.0.0 .\n",[20,67062,67063],{"__ignoreMap":111},[115,67064,67065,67067,67069,67071,67074],{"class":117,"line":118},[115,67066,3295],{"class":262},[115,67068,17022],{"class":132},[115,67070,3909],{"class":202},[115,67072,67073],{"class":132}," myapp:1.0.0",[115,67075,17030],{"class":132},[16,67077,67078],{},"Run it with production-like variables:",[106,67080,67082],{"className":108,"code":67081,"language":110,"meta":111,"style":111},"docker run --rm -p 8000:8000 \\\n  -e DJANGO_SECRET_KEY='replace-me' \\\n  -e DJANGO_ALLOWED_HOSTS='localhost,127.0.0.1' \\\n  -e DJANGO_CSRF_TRUSTED_ORIGINS='http:\u002F\u002Flocalhost,http:\u002F\u002F127.0.0.1' \\\n  -e POSTGRES_DB='appdb' \\\n  -e POSTGRES_USER='appuser' \\\n  -e POSTGRES_PASSWORD='apppassword' \\\n  -e POSTGRES_HOST='host.docker.internal' \\\n  -e POSTGRES_PORT='5432' \\\n  myapp:1.0.0\n",[20,67083,67084,67099,67108,67117,67126,67135,67144,67153,67162,67171],{"__ignoreMap":111},[115,67085,67086,67088,67090,67092,67094,67097],{"class":117,"line":118},[115,67087,3295],{"class":262},[115,67089,18889],{"class":132},[115,67091,18892],{"class":202},[115,67093,1001],{"class":202},[115,67095,67096],{"class":132}," 8000:8000",[115,67098,317],{"class":202},[115,67100,67101,67103,67106],{"class":117,"line":136},[115,67102,18904],{"class":202},[115,67104,67105],{"class":132}," DJANGO_SECRET_KEY='replace-me'",[115,67107,317],{"class":202},[115,67109,67110,67112,67115],{"class":117,"line":149},[115,67111,18904],{"class":202},[115,67113,67114],{"class":132}," DJANGO_ALLOWED_HOSTS='localhost,127.0.0.1'",[115,67116,317],{"class":202},[115,67118,67119,67121,67124],{"class":117,"line":162},[115,67120,18904],{"class":202},[115,67122,67123],{"class":132}," DJANGO_CSRF_TRUSTED_ORIGINS='http:\u002F\u002Flocalhost,http:\u002F\u002F127.0.0.1'",[115,67125,317],{"class":202},[115,67127,67128,67130,67133],{"class":117,"line":175},[115,67129,18904],{"class":202},[115,67131,67132],{"class":132}," POSTGRES_DB='appdb'",[115,67134,317],{"class":202},[115,67136,67137,67139,67142],{"class":117,"line":350},[115,67138,18904],{"class":202},[115,67140,67141],{"class":132}," POSTGRES_USER='appuser'",[115,67143,317],{"class":202},[115,67145,67146,67148,67151],{"class":117,"line":365},[115,67147,18904],{"class":202},[115,67149,67150],{"class":132}," POSTGRES_PASSWORD='apppassword'",[115,67152,317],{"class":202},[115,67154,67155,67157,67160],{"class":117,"line":380},[115,67156,18904],{"class":202},[115,67158,67159],{"class":132}," POSTGRES_HOST='host.docker.internal'",[115,67161,317],{"class":202},[115,67163,67164,67166,67169],{"class":117,"line":487},[115,67165,18904],{"class":202},[115,67167,67168],{"class":132}," POSTGRES_PORT='5432'",[115,67170,317],{"class":202},[115,67172,67173],{"class":117,"line":2095},[115,67174,67175],{"class":132},"  myapp:1.0.0\n",[16,67177,67178,67179,67181,67182,211],{},"In production, ",[20,67180,2725],{}," entries must include the scheme, for example ",[20,67183,2963],{},[16,67185,67186,67187,67190],{},"For local testing this is fine, but in real deployments prefer ",[20,67188,67189],{},"--env-file",", orchestrator secrets, or a secret manager over putting secrets directly in shell history or process arguments.",[16,67192,8572],{},[106,67194,67196],{"className":108,"code":67195,"language":110,"meta":111,"style":111},"docker ps\ndocker logs \u003Ccontainer_id>\ncurl -I http:\u002F\u002Flocalhost:8000\u002Fhealthz\ncurl -I http:\u002F\u002Flocalhost:8000\u002F\n",[20,67197,67198,67204,67220,67229],{"__ignoreMap":111},[115,67199,67200,67202],{"class":117,"line":118},[115,67201,3295],{"class":262},[115,67203,4790],{"class":132},[115,67205,67206,67208,67210,67212,67215,67218],{"class":117,"line":136},[115,67207,3295],{"class":262},[115,67209,3301],{"class":132},[115,67211,7691],{"class":121},[115,67213,67214],{"class":132},"container_i",[115,67216,67217],{"class":125},"d",[115,67219,17380],{"class":121},[115,67221,67222,67224,67226],{"class":117,"line":149},[115,67223,2764],{"class":262},[115,67225,2767],{"class":202},[115,67227,67228],{"class":132}," http:\u002F\u002Flocalhost:8000\u002Fhealthz\n",[115,67230,67231,67233,67235],{"class":117,"line":162},[115,67232,2764],{"class":262},[115,67234,2767],{"class":202},[115,67236,67237],{"class":132}," http:\u002F\u002Flocalhost:8000\u002F\n",[16,67239,67240],{},"Then test:",[63,67242,67243,67245,67247,67249,67251,67256],{},[66,67244,15882],{},[66,67246,1137],{},[66,67248,15888],{},[66,67250,15894],{},[66,67252,67253,67255],{},[20,67254,7350],{}," pages are not exposed",[66,67257,67258,67260],{},[20,67259,32941],{}," returns 200",[11,67262,67264],{"id":67263},"_7-connect-the-container-to-production-services","7. Connect the container to production services",[16,67266,67267],{},"For PostgreSQL and Redis, pass connection details as environment variables from your host, orchestrator, or secret manager. Do not bake them into the image.",[16,67269,67270],{},"For reverse proxy integration, keep Gunicorn bound to an internal port and let Nginx or Caddy handle public traffic.",[16,67272,67273],{},"If Nginx serves static and media from the host, you must mount those paths from the container or sync them during release. A host-level Nginx config is incomplete unless the files actually exist on the host.",[16,67275,67276],{},"Example Nginx upstream:",[106,67278,67280],{"className":2154,"code":67279,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server 127.0.0.1:8000;\n}\n\nserver {\n    listen 80;\n    server_name example.com;\n\n    location \u002Fstatic\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fstaticfiles\u002F;\n    }\n\n    location \u002Fmedia\u002F {\n        alias \u002Fsrv\u002Fmyapp\u002Fmedia\u002F;\n    }\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fdjango_app;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\n",[20,67281,67282,67290,67296,67300,67304,67310,67318,67324,67328,67336,67342,67346,67350,67358,67364,67368,67372,67380,67386,67392,67398,67404,67410,67414],{"__ignoreMap":111},[115,67283,67284,67286,67288],{"class":117,"line":118},[115,67285,52563],{"class":121},[115,67287,52566],{"class":262},[115,67289,2220],{"class":125},[115,67291,67292,67294],{"class":117,"line":136},[115,67293,52573],{"class":121},[115,67295,52576],{"class":125},[115,67297,67298],{"class":117,"line":149},[115,67299,2323],{"class":125},[115,67301,67302],{"class":117,"line":162},[115,67303,310],{"emptyLinePlaceholder":309},[115,67305,67306,67308],{"class":117,"line":175},[115,67307,2163],{"class":121},[115,67309,2166],{"class":125},[115,67311,67312,67314,67316],{"class":117,"line":350},[115,67313,2171],{"class":121},[115,67315,3808],{"class":202},[115,67317,3811],{"class":125},[115,67319,67320,67322],{"class":117,"line":365},[115,67321,2182],{"class":121},[115,67323,2185],{"class":125},[115,67325,67326],{"class":117,"line":380},[115,67327,310],{"emptyLinePlaceholder":309},[115,67329,67330,67332,67334],{"class":117,"line":487},[115,67331,2214],{"class":121},[115,67333,2217],{"class":262},[115,67335,2220],{"class":125},[115,67337,67338,67340],{"class":117,"line":2095},[115,67339,2225],{"class":121},[115,67341,2228],{"class":125},[115,67343,67344],{"class":117,"line":2104},[115,67345,2233],{"class":125},[115,67347,67348],{"class":117,"line":2113},[115,67349,310],{"emptyLinePlaceholder":309},[115,67351,67352,67354,67356],{"class":117,"line":2122},[115,67353,2214],{"class":121},[115,67355,2244],{"class":262},[115,67357,2220],{"class":125},[115,67359,67360,67362],{"class":117,"line":2131},[115,67361,2225],{"class":121},[115,67363,2253],{"class":125},[115,67365,67366],{"class":117,"line":2136},[115,67367,2233],{"class":125},[115,67369,67370],{"class":117,"line":2142},[115,67371,310],{"emptyLinePlaceholder":309},[115,67373,67374,67376,67378],{"class":117,"line":2273},[115,67375,2214],{"class":121},[115,67377,2268],{"class":262},[115,67379,2220],{"class":125},[115,67381,67382,67384],{"class":117,"line":2282},[115,67383,2276],{"class":121},[115,67385,52685],{"class":125},[115,67387,67388,67390],{"class":117,"line":2291},[115,67389,2285],{"class":121},[115,67391,2288],{"class":125},[115,67393,67394,67396],{"class":117,"line":2299},[115,67395,2285],{"class":121},[115,67397,2304],{"class":125},[115,67399,67400,67402],{"class":117,"line":2307},[115,67401,2285],{"class":121},[115,67403,2312],{"class":125},[115,67405,67406,67408],{"class":117,"line":2315},[115,67407,2285],{"class":121},[115,67409,3767],{"class":125},[115,67411,67412],{"class":117,"line":2320},[115,67413,2233],{"class":125},[115,67415,67416],{"class":117,"line":7083},[115,67417,2323],{"class":125},[16,67419,67420],{},"If the proxy serves static and media directly, mount or sync those paths outside the app container. Do not rely on the container filesystem for persistent uploads.",[11,67422,67424],{"id":67423},"_8-use-a-controlled-release-workflow","8. Use a controlled release workflow",[16,67426,67427],{},"A practical release flow looks like this:",[1173,67429,67430,67433,67436,67439,67442,67444,67446,67449],{},[66,67431,67432],{},"build image in CI",[66,67434,67435],{},"tag it with commit SHA or version",[66,67437,67438],{},"push it to a registry",[66,67440,67441],{},"pull it on the server",[66,67443,59753],{},[66,67445,60207],{},[66,67447,67448],{},"start or replace the web container",[66,67450,2868],{},[16,67452,67453],{},"Example tagging:",[106,67455,67457],{"className":108,"code":67456,"language":110,"meta":111,"style":111},"docker build -t registry.example.com\u002Fmyapp:gitsha123 .\ndocker push registry.example.com\u002Fmyapp:gitsha123\n",[20,67458,67459,67472],{"__ignoreMap":111},[115,67460,67461,67463,67465,67467,67470],{"class":117,"line":118},[115,67462,3295],{"class":262},[115,67464,17022],{"class":132},[115,67466,3909],{"class":202},[115,67468,67469],{"class":132}," registry.example.com\u002Fmyapp:gitsha123",[115,67471,17030],{"class":132},[115,67473,67474,67476,67478],{"class":117,"line":136},[115,67475,3295],{"class":262},[115,67477,19112],{"class":132},[115,67479,67480],{"class":132}," registry.example.com\u002Fmyapp:gitsha123\n",[16,67482,67483],{},"Server-side deploy:",[106,67485,67487],{"className":108,"code":67486,"language":110,"meta":111,"style":111},"docker pull registry.example.com\u002Fmyapp:gitsha123\n\ndocker run --rm \\\n  --env-file \u002Fsrv\u002Fmyapp\u002F.env \\\n  -v \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles \\\n  -v \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia \\\n  registry.example.com\u002Fmyapp:gitsha123 \\\n  python manage.py check --deploy\n\ndocker run --rm \\\n  --env-file \u002Fsrv\u002Fmyapp\u002F.env \\\n  -v \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles \\\n  -v \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia \\\n  registry.example.com\u002Fmyapp:gitsha123 \\\n  python manage.py migrate --noinput\n\ndocker run --rm \\\n  --env-file \u002Fsrv\u002Fmyapp\u002F.env \\\n  -v \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles \\\n  -v \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia \\\n  registry.example.com\u002Fmyapp:gitsha123 \\\n  python manage.py collectstatic --noinput\n\ndocker stop myapp || true\ndocker rm myapp || true\n\ndocker run -d --name myapp --restart unless-stopped \\\n  --env-file \u002Fsrv\u002Fmyapp\u002F.env \\\n  -v \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles \\\n  -v \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia \\\n  -p 127.0.0.1:8000:8000 \\\n  registry.example.com\u002Fmyapp:gitsha123\n",[20,67488,67489,67498,67502,67512,67522,67532,67541,67548,67558,67562,67572,67580,67588,67596,67602,67612,67616,67626,67634,67642,67650,67656,67666,67670,67683,67695,67699,67720,67728,67736,67744,67753],{"__ignoreMap":111},[115,67490,67491,67493,67496],{"class":117,"line":118},[115,67492,3295],{"class":262},[115,67494,67495],{"class":132}," pull",[115,67497,67480],{"class":132},[115,67499,67500],{"class":117,"line":136},[115,67501,310],{"emptyLinePlaceholder":309},[115,67503,67504,67506,67508,67510],{"class":117,"line":149},[115,67505,3295],{"class":262},[115,67507,18889],{"class":132},[115,67509,18892],{"class":202},[115,67511,317],{"class":202},[115,67513,67514,67517,67520],{"class":117,"line":162},[115,67515,67516],{"class":202},"  --env-file",[115,67518,67519],{"class":132}," \u002Fsrv\u002Fmyapp\u002F.env",[115,67521,317],{"class":202},[115,67523,67524,67527,67530],{"class":117,"line":175},[115,67525,67526],{"class":202},"  -v",[115,67528,67529],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles",[115,67531,317],{"class":202},[115,67533,67534,67536,67539],{"class":117,"line":350},[115,67535,67526],{"class":202},[115,67537,67538],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia",[115,67540,317],{"class":202},[115,67542,67543,67546],{"class":117,"line":365},[115,67544,67545],{"class":132},"  registry.example.com\u002Fmyapp:gitsha123",[115,67547,317],{"class":202},[115,67549,67550,67552,67554,67556],{"class":117,"line":380},[115,67551,67007],{"class":132},[115,67553,1117],{"class":132},[115,67555,1814],{"class":132},[115,67557,1817],{"class":202},[115,67559,67560],{"class":117,"line":487},[115,67561,310],{"emptyLinePlaceholder":309},[115,67563,67564,67566,67568,67570],{"class":117,"line":2095},[115,67565,3295],{"class":262},[115,67567,18889],{"class":132},[115,67569,18892],{"class":202},[115,67571,317],{"class":202},[115,67573,67574,67576,67578],{"class":117,"line":2104},[115,67575,67516],{"class":202},[115,67577,67519],{"class":132},[115,67579,317],{"class":202},[115,67581,67582,67584,67586],{"class":117,"line":2113},[115,67583,67526],{"class":202},[115,67585,67529],{"class":132},[115,67587,317],{"class":202},[115,67589,67590,67592,67594],{"class":117,"line":2122},[115,67591,67526],{"class":202},[115,67593,67538],{"class":132},[115,67595,317],{"class":202},[115,67597,67598,67600],{"class":117,"line":2131},[115,67599,67545],{"class":132},[115,67601,317],{"class":202},[115,67603,67604,67606,67608,67610],{"class":117,"line":2136},[115,67605,67007],{"class":132},[115,67607,1117],{"class":132},[115,67609,1826],{"class":132},[115,67611,1841],{"class":202},[115,67613,67614],{"class":117,"line":2142},[115,67615,310],{"emptyLinePlaceholder":309},[115,67617,67618,67620,67622,67624],{"class":117,"line":2273},[115,67619,3295],{"class":262},[115,67621,18889],{"class":132},[115,67623,18892],{"class":202},[115,67625,317],{"class":202},[115,67627,67628,67630,67632],{"class":117,"line":2282},[115,67629,67516],{"class":202},[115,67631,67519],{"class":132},[115,67633,317],{"class":202},[115,67635,67636,67638,67640],{"class":117,"line":2291},[115,67637,67526],{"class":202},[115,67639,67529],{"class":132},[115,67641,317],{"class":202},[115,67643,67644,67646,67648],{"class":117,"line":2299},[115,67645,67526],{"class":202},[115,67647,67538],{"class":132},[115,67649,317],{"class":202},[115,67651,67652,67654],{"class":117,"line":2307},[115,67653,67545],{"class":132},[115,67655,317],{"class":202},[115,67657,67658,67660,67662,67664],{"class":117,"line":2315},[115,67659,67007],{"class":132},[115,67661,1117],{"class":132},[115,67663,1838],{"class":132},[115,67665,1841],{"class":202},[115,67667,67668],{"class":117,"line":2320},[115,67669,310],{"emptyLinePlaceholder":309},[115,67671,67672,67674,67676,67678,67680],{"class":117,"line":7083},[115,67673,3295],{"class":262},[115,67675,9987],{"class":132},[115,67677,5325],{"class":132},[115,67679,43235],{"class":121},[115,67681,67682],{"class":202}," true\n",[115,67684,67685,67687,67689,67691,67693],{"class":117,"line":7090},[115,67686,3295],{"class":262},[115,67688,15719],{"class":132},[115,67690,5325],{"class":132},[115,67692,43235],{"class":121},[115,67694,67682],{"class":202},[115,67696,67697],{"class":117,"line":7097},[115,67698,310],{"emptyLinePlaceholder":309},[115,67700,67701,67703,67705,67707,67710,67712,67715,67718],{"class":117,"line":7108},[115,67702,3295],{"class":262},[115,67704,18889],{"class":132},[115,67706,1019],{"class":202},[115,67708,67709],{"class":202}," --name",[115,67711,5325],{"class":132},[115,67713,67714],{"class":202}," --restart",[115,67716,67717],{"class":132}," unless-stopped",[115,67719,317],{"class":202},[115,67721,67722,67724,67726],{"class":117,"line":7113},[115,67723,67516],{"class":202},[115,67725,67519],{"class":132},[115,67727,317],{"class":202},[115,67729,67730,67732,67734],{"class":117,"line":16535},[115,67731,67526],{"class":202},[115,67733,67529],{"class":132},[115,67735,317],{"class":202},[115,67737,67738,67740,67742],{"class":117,"line":16544},[115,67739,67526],{"class":202},[115,67741,67538],{"class":132},[115,67743,317],{"class":202},[115,67745,67746,67748,67751],{"class":117,"line":16549},[115,67747,338],{"class":202},[115,67749,67750],{"class":132}," 127.0.0.1:8000:8000",[115,67752,317],{"class":202},[115,67754,67755],{"class":117,"line":16555},[115,67756,67757],{"class":132},"  registry.example.com\u002Fmyapp:gitsha123\n",[16,67759,67760],{},"Rollback is the same process with the previous known-good tag:",[106,67762,67764],{"className":108,"code":67763,"language":110,"meta":111,"style":111},"docker pull registry.example.com\u002Fmyapp:previoussha\ndocker stop myapp && docker rm myapp\ndocker run -d --name myapp --restart unless-stopped \\\n  --env-file \u002Fsrv\u002Fmyapp\u002F.env \\\n  -v \u002Fsrv\u002Fmyapp\u002Fstaticfiles:\u002Fapp\u002Fstaticfiles \\\n  -v \u002Fsrv\u002Fmyapp\u002Fmedia:\u002Fapp\u002Fmedia \\\n  -p 127.0.0.1:8000:8000 \\\n  registry.example.com\u002Fmyapp:previoussha\n",[20,67765,67766,67775,67792,67810,67818,67826,67834,67842],{"__ignoreMap":111},[115,67767,67768,67770,67772],{"class":117,"line":118},[115,67769,3295],{"class":262},[115,67771,67495],{"class":132},[115,67773,67774],{"class":132}," registry.example.com\u002Fmyapp:previoussha\n",[115,67776,67777,67779,67781,67783,67785,67787,67789],{"class":117,"line":136},[115,67778,3295],{"class":262},[115,67780,9987],{"class":132},[115,67782,5325],{"class":132},[115,67784,3912],{"class":125},[115,67786,3295],{"class":262},[115,67788,15719],{"class":132},[115,67790,67791],{"class":132}," myapp\n",[115,67793,67794,67796,67798,67800,67802,67804,67806,67808],{"class":117,"line":149},[115,67795,3295],{"class":262},[115,67797,18889],{"class":132},[115,67799,1019],{"class":202},[115,67801,67709],{"class":202},[115,67803,5325],{"class":132},[115,67805,67714],{"class":202},[115,67807,67717],{"class":132},[115,67809,317],{"class":202},[115,67811,67812,67814,67816],{"class":117,"line":162},[115,67813,67516],{"class":202},[115,67815,67519],{"class":132},[115,67817,317],{"class":202},[115,67819,67820,67822,67824],{"class":117,"line":175},[115,67821,67526],{"class":202},[115,67823,67529],{"class":132},[115,67825,317],{"class":202},[115,67827,67828,67830,67832],{"class":117,"line":350},[115,67829,67526],{"class":202},[115,67831,67538],{"class":132},[115,67833,317],{"class":202},[115,67835,67836,67838,67840],{"class":117,"line":365},[115,67837,338],{"class":202},[115,67839,67750],{"class":132},[115,67841,317],{"class":202},[115,67843,67844],{"class":117,"line":380},[115,67845,67846],{"class":132},"  registry.example.com\u002Fmyapp:previoussha\n",[16,67848,67849],{},"Verification after deploy:",[106,67851,67853],{"className":108,"code":67852,"language":110,"meta":111,"style":111},"docker logs myapp --tail 50\ncurl -I http:\u002F\u002F127.0.0.1:8000\u002Fhealthz\n",[20,67854,67855,67867],{"__ignoreMap":111},[115,67856,67857,67859,67861,67863,67865],{"class":117,"line":118},[115,67858,3295],{"class":262},[115,67860,3301],{"class":132},[115,67862,5325],{"class":132},[115,67864,38217],{"class":202},[115,67866,19841],{"class":202},[115,67868,67869,67871,67873],{"class":117,"line":136},[115,67870,2764],{"class":262},[115,67872,2767],{"class":202},[115,67874,53902],{"class":132},[16,67876,67877],{},"If the failed release included an incompatible migration, application rollback may not be enough. Test backward-compatible migrations where possible, and keep a separate database recovery plan.",[52,67879,41766],{"id":41765},[16,67881,67882],{},"Once you are repeating the same build, tag, push, validate, migrate, collect static files, deploy, and health-check steps across environments, convert them into scripts or CI templates. The best first automation targets are image tagging, environment validation, migration execution, post-deploy health checks, and rollback to a previous image tag.",[11,67884,1321],{"id":1320},[16,67886,67887],{},"This setup works because it separates image contents from runtime state.",[16,67889,67890],{},"The image contains code and dependencies. The environment provides secrets and service endpoints. PostgreSQL, Redis, TLS, and persistent media remain external so the container can be replaced safely at any time.",[16,67892,67893],{},"Containers improve repeatability, but they do not replace production architecture. You still need a reverse proxy strategy, persistent storage decisions, migration discipline, and monitoring.",[16,67895,67896],{},"Secrets should never be baked into images because anyone with image access can inspect them later. Media should not live in the container filesystem because container replacement deletes local writable state unless you add external volumes or object storage.",[16,67898,67899],{},"Rollback depends on two things:",[63,67901,67902,67905],{},[66,67903,67904],{},"immutable image tags you can redeploy",[66,67906,67907],{},"migration changes that do not trap you in a broken schema state",[11,67909,30532],{"id":30531},[63,67911,67912,67918,67923,67930,67935,67941,67947],{},[66,67913,67914,67917],{},[1226,67915,67916],{},"Celery workers and beat:"," run them as separate containers from the same image with different commands.",[66,67919,67920,67922],{},[1226,67921,30569],{}," if you use Django ASGI features, replace Gunicorn WSGI startup with Gunicorn plus a Uvicorn worker, or run Uvicorn directly where appropriate.",[66,67924,67925,2957,67927,67929],{},[1226,67926,10126],{},[20,67928,13689],{}," can happen during image build if static output is environment-independent. If storage credentials, manifest generation, or host-mounted files are part of the release process, run it during deployment instead.",[66,67931,67932,67934],{},[1226,67933,63274],{}," a real HTTP health endpoint is better than a framework configuration check because it proves the app server is actually responding.",[66,67936,67937,67940],{},[1226,67938,67939],{},"Trusted proxy headers:"," only enable forwarded-host and forwarded-proto handling when your reverse proxy is under your control and configured correctly.",[66,67942,67943,67946],{},[1226,67944,67945],{},"Rootless environments:"," some platforms enforce non-root containers already. Keep ownership and writable paths explicit.",[66,67948,67949,67952],{},[1226,67950,67951],{},"Private Python packages:"," use your CI secret mechanism or build-time secret support carefully. Do not hardcode repository credentials in the Dockerfile.",[11,67954,1386],{"id":1385},[16,67956,67957,67958,211],{},"For the settings hardening behind this container setup, see ",[1395,67959,3000],{"href":2999},[16,67961,67962,67963,211],{},"If you want the app server and reverse proxy layer built out fully, see ",[1395,67964,2986],{"href":2985},[16,67966,67967,67968,211],{},"If you are deploying an ASGI stack, see ",[1395,67969,8039],{"href":8038},[16,67971,67972,67973,211],{},"For an alternative reverse proxy with automatic TLS, see ",[1395,67974,8046],{"href":8045},[11,67976,1420],{"id":1419},[52,67978,67980,67981,67983],{"id":67979},"should-collectstatic-happen-during-build-or-deploy","Should ",[20,67982,13689],{}," happen during build or deploy?",[16,67985,67986],{},"If static assets are fully deterministic and do not depend on runtime secrets, build time is fine. If static collection depends on environment-specific settings, external storage credentials, or host-mounted output paths, run it during the release step instead. Keep the choice explicit and documented.",[52,67988,67990],{"id":67989},"should-migrations-run-inside-the-container-entrypoint","Should migrations run inside the container entrypoint?",[16,67992,67993,67994,211],{},"Usually not on every startup. It is safer to run migrations as a controlled release step, especially when you run multiple replicas. If you do allow entrypoint migrations, gate them behind an environment variable like ",[20,67995,67043],{},[52,67997,67999],{"id":67998},"do-i-need-nginx-or-caddy-in-front-of-a-django-docker-container","Do I need Nginx or Caddy in front of a Django Docker container?",[16,68001,68002],{},"Often yes. A reverse proxy is useful for TLS termination, buffering, request limits, forwarded headers, and static\u002Fmedia routing. Some managed platforms provide this layer for you, but plain Docker on a server usually needs it.",[52,68004,68006],{"id":68005},"can-i-store-uploaded-media-inside-the-container","Can I store uploaded media inside the container?",[16,68008,68009],{},"Do not rely on that in production. Container filesystems are disposable. Use a mounted persistent volume or object storage instead.",[52,68011,68013],{"id":68012},"how-do-i-roll-back-a-bad-dockerized-django-release","How do I roll back a bad Dockerized Django release?",[16,68015,68016],{},"Redeploy the previous image tag and restart the container. This is why every release should be tagged immutably. If the release also changed the database schema, image rollback alone may not be enough, so plan database recovery separately.",[1485,68018,68019],{},"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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":68021},[68022,68023,68024,68025,68026,68027,68029,68030,68031,68032,68033,68036,68037,68038,68039],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":65770,"depth":136,"text":65771},{"id":65808,"depth":136,"text":65809},{"id":66395,"depth":136,"text":68028},"3. Add a .dockerignore",{"id":66483,"depth":136,"text":66484},{"id":66834,"depth":136,"text":66835},{"id":67053,"depth":136,"text":67054},{"id":67263,"depth":136,"text":67264},{"id":67423,"depth":136,"text":67424,"children":68034},[68035],{"id":41765,"depth":149,"text":41766},{"id":1320,"depth":136,"text":1321},{"id":30531,"depth":136,"text":30532},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":68040},[68041,68043,68044,68045,68046],{"id":67979,"depth":149,"text":68042},"Should collectstatic happen during build or deploy?",{"id":67989,"depth":149,"text":67990},{"id":67998,"depth":149,"text":67999},{"id":68005,"depth":149,"text":68006},{"id":68012,"depth":149,"text":68013},{},"\u002Fdockerize-django-production",[2992,68050,11637],"\u002Fdeploy\u002Fdeploy-django-on-google-cloud-run",{"title":65683,"description":65690},[1557,3295],"dockerize-django-production",[1557,3295],"_sFNEqBSlNXIpbtZDpJXQcYAzzNa_2E-s-a6LQOyI1s",{"id":68057,"title":4446,"body":68058,"category":4543,"description":69435,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":4546,"meta":69436,"navigation":309,"path":69437,"priority":35628,"related":69438,"role":4552,"section":4553,"seo":69439,"stack":69440,"stem":69441,"tags":69442,"type":4558,"__hash__":69443},"articles\u002Ffix-disallowedhost-django-production.md",{"type":8,"value":68059,"toc":69394},[68060,68062,68070,68073,68091,68094,68110,68113,68115,68121,68151,68156,68158,68162,68165,68168,68188,68191,68208,68211,68217,68220,68236,68239,68263,68265,68280,68287,68290,68295,68323,68326,68362,68365,68368,68428,68431,68443,68446,68479,68482,68504,68506,68529,68533,68540,68542,68545,68621,68623,68635,68637,68651,68656,68671,68674,68694,68699,68701,68704,68721,68724,68727,68747,68749,68779,68785,68789,68792,68795,68816,68818,68835,68838,68856,68859,68861,68875,68879,68882,68884,68887,68901,68903,68917,68919,68940,68944,68969,68971,68974,68992,68994,69010,69012,69033,69037,69040,69062,69065,69081,69084,69087,69107,69113,69115,69121,69124,69129,69138,69166,69172,69174,69178,69181,69197,69206,69210,69219,69223,69234,69238,69248,69252,69258,69262,69271,69291,69294,69298,69310,69312,69317,69325,69331,69333,69342,69345,69351,69354,69361,69369,69376,69382,69388,69391],[11,68061,14],{"id":13},[16,68063,2885,68064,68066,68067,68069],{},[20,68065,46086],{}," error in Django production means Django rejected the incoming ",[20,68068,3648],{}," header. This is a security check, not a random app failure.",[16,68071,68072],{},"Common symptoms include:",[63,68074,68075,68080,68085,68088],{},[66,68076,68077],{},[20,68078,68079],{},"Invalid HTTP_HOST header: 'example.com'",[66,68081,68082,68083],{},"HTTP ",[20,68084,41158],{},[66,68086,68087],{},"the app works locally but fails on the real domain",[66,68089,68090],{},"the error starts after adding Nginx, Gunicorn, Docker, a load balancer, or a CDN",[16,68092,68093],{},"In production, this usually comes down to one of three problems:",[1173,68095,68096,68101,68107],{},[66,68097,68098,68099],{},"the real hostname is missing from ",[20,68100,2719],{},[66,68102,68103,68104,68106],{},"the reverse proxy is not forwarding the original ",[20,68105,3648],{}," header correctly",[66,68108,68109],{},"DNS or routing is sending traffic somewhere unexpected",[16,68111,68112],{},"The fix is to identify the exact host Django is rejecting, allow only the hostnames you actually serve, and verify that your proxy and DNS match that configuration.",[11,68114,30],{"id":29},[16,68116,68117,68118,68120],{},"To apply a practical Django ",[20,68119,46086],{}," fix in production:",[63,68122,68123,68128,68134,68140,68143,68146],{},[66,68124,68125,68126],{},"add your real production domains to ",[20,68127,2719],{},[66,68129,68130,68131,68133],{},"include both apex and ",[20,68132,4246],{}," if both are in use",[66,68135,68136,68137,68139],{},"make sure Nginx or Caddy forwards the original ",[20,68138,3648],{}," header",[66,68141,68142],{},"verify DNS points to the correct server or load balancer",[66,68144,68145],{},"restart or reload the Django app process after changing settings",[66,68147,68148,68149],{},"test with both browser and ",[20,68150,2764],{},[16,68152,50661,68153,68155],{},[20,68154,41912],{}," unless you fully understand the security tradeoff.",[11,68157,43],{"id":42},[11,68159,68161],{"id":68160},"step-1-confirm-the-exact-host-django-is-rejecting","Step 1 - Confirm the exact host Django is rejecting",[16,68163,68164],{},"Check your Django, Gunicorn, or application logs first.",[16,68166,68167],{},"If you use systemd with Gunicorn:",[106,68169,68170],{"className":108,"code":59564,"language":110,"meta":111,"style":111},[20,68171,68172],{"__ignoreMap":111},[115,68173,68174,68176,68178,68180,68182,68184,68186],{"class":117,"line":118},[115,68175,2001],{"class":262},[115,68177,5030],{"class":132},[115,68179,2788],{"class":202},[115,68181,2791],{"class":132},[115,68183,2794],{"class":202},[115,68185,2797],{"class":202},[115,68187,2800],{"class":202},[16,68189,68190],{},"If you want to follow logs live:",[106,68192,68194],{"className":108,"code":68193,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -f\n",[20,68195,68196],{"__ignoreMap":111},[115,68197,68198,68200,68202,68204,68206],{"class":117,"line":118},[115,68199,2001],{"class":262},[115,68201,5030],{"class":132},[115,68203,2788],{"class":202},[115,68205,2791],{"class":132},[115,68207,36482],{"class":202},[16,68209,68210],{},"Typical error:",[106,68212,68215],{"className":68213,"code":68214,"language":247,"meta":111},[245],"Invalid HTTP_HOST header: 'www.example.com'. You may need to add 'www.example.com' to ALLOWED_HOSTS.\n",[20,68216,68214],{"__ignoreMap":111},[16,68218,68219],{},"If running in Docker:",[106,68221,68222],{"className":108,"code":3288,"language":110,"meta":111,"style":111},[20,68223,68224],{"__ignoreMap":111},[115,68225,68226,68228,68230,68232,68234],{"class":117,"line":118},[115,68227,3295],{"class":262},[115,68229,3298],{"class":132},[115,68231,3301],{"class":132},[115,68233,3304],{"class":132},[115,68235,3307],{"class":202},[16,68237,68238],{},"What to confirm:",[63,68240,68241,68246,68251,68257,68260],{},[66,68242,68243,68244],{},"is the rejected host the apex domain, like ",[20,68245,3145],{},[66,68247,68248,68249],{},"is it ",[20,68250,3149],{},[66,68252,68253,68254],{},"is it a subdomain like ",[20,68255,68256],{},"api.example.com",[66,68258,68259],{},"is it the server IP address",[66,68261,68262],{},"does it happen for all requests or only one hostname",[16,68264,3515],{},[63,68266,68268,68274],{"className":68267},[48613],[66,68269,68271,68273],{"className":68270},[48617],[48619,68272],{"disabled":309,"type":48621}," you identified the exact rejected hostname from logs",[66,68275,68277,68279],{"className":68276},[48617],[48619,68278],{"disabled":309,"type":48621}," you did not guess and add extra hosts blindly",[11,68281,68283,68284,68286],{"id":68282},"step-2-configure-allowed_hosts-correctly","Step 2 - Configure ",[20,68285,2719],{}," correctly",[16,68288,68289],{},"Add every intended production hostname explicitly.",[16,68291,68292,68293,241],{},"Example for one domain with ",[20,68294,4246],{},[106,68296,68297],{"className":2369,"code":3529,"language":1114,"meta":111,"style":111},[20,68298,68299,68307,68313,68319],{"__ignoreMap":111},[115,68300,68301,68303,68305],{"class":117,"line":118},[115,68302,2719],{"class":202},[115,68304,2380],{"class":121},[115,68306,3540],{"class":125},[115,68308,68309,68311],{"class":117,"line":136},[115,68310,3545],{"class":132},[115,68312,3354],{"class":125},[115,68314,68315,68317],{"class":117,"line":149},[115,68316,3552],{"class":132},[115,68318,3354],{"class":125},[115,68320,68321],{"class":117,"line":162},[115,68322,2552],{"class":125},[16,68324,68325],{},"If you also serve an app subdomain:",[106,68327,68329],{"className":2369,"code":68328,"language":1114,"meta":111,"style":111},"ALLOWED_HOSTS = [\n    \"example.com\",\n    \"www.example.com\",\n    \"app.example.com\",\n]\n",[20,68330,68331,68339,68345,68351,68358],{"__ignoreMap":111},[115,68332,68333,68335,68337],{"class":117,"line":118},[115,68334,2719],{"class":202},[115,68336,2380],{"class":121},[115,68338,3540],{"class":125},[115,68340,68341,68343],{"class":117,"line":136},[115,68342,3545],{"class":132},[115,68344,3354],{"class":125},[115,68346,68347,68349],{"class":117,"line":149},[115,68348,3552],{"class":132},[115,68350,3354],{"class":125},[115,68352,68353,68356],{"class":117,"line":162},[115,68354,68355],{"class":132},"    \"app.example.com\"",[115,68357,3354],{"class":125},[115,68359,68360],{"class":117,"line":175},[115,68361,2552],{"class":125},[16,68363,68364],{},"Avoid broad catch-all patterns unless your architecture truly requires them.",[16,68366,68367],{},"A safe environment-variable pattern:",[106,68369,68371],{"className":2369,"code":68370,"language":1114,"meta":111,"style":111},"import os\n\nALLOWED_HOSTS = [\n    host.strip()\n    for host in os.environ.get(\"DJANGO_ALLOWED_HOSTS\", \"\").split(\",\")\n    if host.strip()\n]\n",[20,68372,68373,68379,68383,68391,68396,68418,68424],{"__ignoreMap":111},[115,68374,68375,68377],{"class":117,"line":118},[115,68376,5613],{"class":121},[115,68378,5616],{"class":125},[115,68380,68381],{"class":117,"line":136},[115,68382,310],{"emptyLinePlaceholder":309},[115,68384,68385,68387,68389],{"class":117,"line":149},[115,68386,2719],{"class":202},[115,68388,2380],{"class":121},[115,68390,3540],{"class":125},[115,68392,68393],{"class":117,"line":162},[115,68394,68395],{"class":125},"    host.strip()\n",[115,68397,68398,68400,68402,68404,68406,68408,68410,68412,68414,68416],{"class":117,"line":175},[115,68399,40952],{"class":121},[115,68401,60404],{"class":125},[115,68403,18262],{"class":121},[115,68405,8884],{"class":125},[115,68407,25202],{"class":132},[115,68409,1153],{"class":125},[115,68411,18272],{"class":132},[115,68413,18275],{"class":125},[115,68415,18278],{"class":132},[115,68417,2394],{"class":125},[115,68419,68420,68422],{"class":117,"line":350},[115,68421,40975],{"class":121},[115,68423,60425],{"class":125},[115,68425,68426],{"class":117,"line":365},[115,68427,2552],{"class":125},[16,68429,68430],{},"Production environment value:",[106,68432,68433],{"className":108,"code":15020,"language":110,"meta":111,"style":111},[20,68434,68435],{"__ignoreMap":111},[115,68436,68437,68439,68441],{"class":117,"line":118},[115,68438,45407],{"class":125},[115,68440,129],{"class":121},[115,68442,63843],{"class":132},[16,68444,68445],{},"If you use systemd, this might live in an environment file or unit override. If you use Docker Compose:",[106,68447,68449],{"className":2485,"code":68448,"language":2487,"meta":111,"style":111},"services:\n  web:\n    environment:\n      DJANGO_ALLOWED_HOSTS: \"example.com,www.example.com\"\n",[20,68450,68451,68457,68463,68469],{"__ignoreMap":111},[115,68452,68453,68455],{"class":117,"line":118},[115,68454,2495],{"class":2494},[115,68456,2498],{"class":125},[115,68458,68459,68461],{"class":117,"line":136},[115,68460,2503],{"class":2494},[115,68462,2498],{"class":125},[115,68464,68465,68467],{"class":117,"line":149},[115,68466,27844],{"class":2494},[115,68468,2498],{"class":125},[115,68470,68471,68474,68476],{"class":117,"line":162},[115,68472,68473],{"class":2494},"      DJANGO_ALLOWED_HOSTS",[115,68475,2513],{"class":125},[115,68477,68478],{"class":132},"\"example.com,www.example.com\"\n",[16,68480,68481],{},"Important:",[63,68483,68484,68492,68495,68498],{},[66,68485,68486,68487,3146,68489,68491],{},"include both ",[20,68488,3145],{},[20,68490,3149],{}," if both should work",[66,68493,68494],{},"do not add random internal names unless they are genuinely used",[66,68496,68497],{},"do not rely on local development values in production",[66,68499,68500,68501,68503],{},"assume ",[20,68502,2707],{}," in production and verify you are editing the settings actually used by the running process",[16,68505,3515],{},[63,68507,68509,68517,68523],{"className":68508},[48613],[66,68510,68512,2957,68514,68516],{"className":68511},[48617],[48619,68513],{"disabled":309,"type":48621},[20,68515,2719],{}," includes every real production hostname",[66,68518,68520,68522],{"className":68519},[48617],[48619,68521],{"disabled":309,"type":48621}," unintended hostnames were not added",[66,68524,68526,68528],{"className":68525},[48617],[48619,68527],{"disabled":309,"type":48621}," env-based settings were updated in the actual production runtime",[11,68530,68532],{"id":68531},"step-3-check-reverse-proxy-host-forwarding","Step 3 - Check reverse proxy host forwarding",[16,68534,68535,68536,68539],{},"A common ",[20,68537,68538],{},"Django Invalid HTTP_HOST header fix"," is correcting proxy headers. Django can only validate the host it receives.",[52,68541,1647],{"id":2156},[16,68543,68544],{},"Your Nginx server block should match the domain and pass the original host upstream.",[106,68546,68548],{"className":2154,"code":68547,"language":2156,"meta":111,"style":111},"server {\n    listen 80;\n    server_name example.com www.example.com;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:8000;\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n",[20,68549,68550,68556,68564,68570,68574,68582,68588,68595,68601,68607,68613,68617],{"__ignoreMap":111},[115,68551,68552,68554],{"class":117,"line":118},[115,68553,2163],{"class":121},[115,68555,2166],{"class":125},[115,68557,68558,68560,68562],{"class":117,"line":136},[115,68559,2171],{"class":121},[115,68561,3808],{"class":202},[115,68563,3811],{"class":125},[115,68565,68566,68568],{"class":117,"line":149},[115,68567,2182],{"class":121},[115,68569,3713],{"class":125},[115,68571,68572],{"class":117,"line":162},[115,68573,310],{"emptyLinePlaceholder":309},[115,68575,68576,68578,68580],{"class":117,"line":175},[115,68577,2214],{"class":121},[115,68579,2268],{"class":262},[115,68581,2220],{"class":125},[115,68583,68584,68586],{"class":117,"line":350},[115,68585,2276],{"class":121},[115,68587,3748],{"class":125},[115,68589,68590,68592],{"class":117,"line":365},[115,68591,2285],{"class":121},[115,68593,68594],{"class":125},"Host $http_host;\n",[115,68596,68597,68599],{"class":117,"line":380},[115,68598,2285],{"class":121},[115,68600,3767],{"class":125},[115,68602,68603,68605],{"class":117,"line":487},[115,68604,2285],{"class":121},[115,68606,2312],{"class":125},[115,68608,68609,68611],{"class":117,"line":2095},[115,68610,2285],{"class":121},[115,68612,2304],{"class":125},[115,68614,68615],{"class":117,"line":2104},[115,68616,2233],{"class":125},[115,68618,68619],{"class":117,"line":2113},[115,68620,2323],{"class":125},[16,68622,42678],{},[106,68624,68625],{"className":108,"code":4271,"language":110,"meta":111,"style":111},[20,68626,68627],{"__ignoreMap":111},[115,68628,68629,68631,68633],{"class":117,"line":118},[115,68630,2001],{"class":262},[115,68632,3906],{"class":132},[115,68634,4282],{"class":202},[16,68636,24117],{},[106,68638,68639],{"className":108,"code":50304,"language":110,"meta":111,"style":111},[20,68640,68641],{"__ignoreMap":111},[115,68642,68643,68645,68647,68649],{"class":117,"line":118},[115,68644,2001],{"class":262},[115,68646,3480],{"class":132},[115,68648,3919],{"class":132},[115,68650,1996],{"class":132},[16,68652,68653,68654,241],{},"Common Nginx mistakes that trigger ",[20,68655,46086],{},[63,68657,68658,68663,68666],{},[66,68659,20107,68660],{},[20,68661,68662],{},"proxy_set_header Host $http_host",[66,68664,68665],{},"proxying with a hardcoded upstream host header",[66,68667,68668,68670],{},[20,68669,49803],{}," does not include the actual domain being served",[16,68672,68673],{},"If TLS terminates at Nginx and Django receives plain HTTP upstream, set this in Django so secure requests are detected correctly:",[106,68675,68676],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,68677,68678],{"__ignoreMap":111},[115,68679,68680,68682,68684,68686,68688,68690,68692],{"class":117,"line":118},[115,68681,2377],{"class":202},[115,68683,2380],{"class":121},[115,68685,2383],{"class":125},[115,68687,2386],{"class":132},[115,68689,1153],{"class":125},[115,68691,2391],{"class":132},[115,68693,2394],{"class":125},[16,68695,68696,68697,34606],{},"Only do this when Django is behind a trusted proxy that sets ",[20,68698,3203],{},[52,68700,24665],{"id":23953},[16,68702,68703],{},"A basic Caddy setup is usually straightforward, but still verify the site label matches the real hostnames.",[106,68705,68707],{"className":23951,"code":68706,"language":23953,"meta":111,"style":111},"example.com, www.example.com {\n    reverse_proxy 127.0.0.1:8000\n}\n",[20,68708,68709,68713,68717],{"__ignoreMap":111},[115,68710,68711],{"class":117,"line":118},[115,68712,23960],{},[115,68714,68715],{"class":117,"line":136},[115,68716,24002],{},[115,68718,68719],{"class":117,"line":149},[115,68720,2323],{},[16,68722,68723],{},"Caddy usually preserves expected host behavior for standard reverse proxy use, but you should still confirm the request reaching Django uses the public hostname.",[16,68725,68726],{},"If TLS terminates at Caddy and Django sees plain HTTP on the upstream side, the same Django setting applies:",[106,68728,68729],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,68730,68731],{"__ignoreMap":111},[115,68732,68733,68735,68737,68739,68741,68743,68745],{"class":117,"line":118},[115,68734,2377],{"class":202},[115,68736,2380],{"class":121},[115,68738,2383],{"class":125},[115,68740,2386],{"class":132},[115,68742,1153],{"class":125},[115,68744,2391],{"class":132},[115,68746,2394],{"class":125},[16,68748,17389],{},[63,68750,68752,68758,68767,68773],{"className":68751},[48613],[66,68753,68755,68757],{"className":68754},[48617],[48619,68756],{"disabled":309,"type":48621}," the proxy serves the intended domain names",[66,68759,68761,68763,68764,68766],{"className":68760},[48617],[48619,68762],{"disabled":309,"type":48621}," the original ",[20,68765,3648],{}," header is forwarded",[66,68768,68770,68772],{"className":68769},[48617],[48619,68771],{"disabled":309,"type":48621}," proxy syntax was tested before reload",[66,68774,68776,68778],{"className":68775},[48617],[48619,68777],{"disabled":309,"type":48621}," secure proxy headers are configured consistently if TLS terminates before Django",[16,68780,68781,68782,68784],{},"Rollback note: if proxy edits break traffic, restore the previous working config from backup or version control, run ",[20,68783,7611],{},", and reload again.",[11,68786,68788],{"id":68787},"step-4-verify-dns-and-domain-routing","Step 4 - Verify DNS and domain routing",[16,68790,68791],{},"Make sure the hostname actually points where you think it does.",[16,68793,68794],{},"Check DNS:",[106,68796,68798],{"className":108,"code":68797,"language":110,"meta":111,"style":111},"dig example.com +short\ndig www.example.com +short\n",[20,68799,68800,68808],{"__ignoreMap":111},[115,68801,68802,68804,68806],{"class":117,"line":118},[115,68803,6431],{"class":262},[115,68805,6434],{"class":132},[115,68807,6437],{"class":132},[115,68809,68810,68812,68814],{"class":117,"line":136},[115,68811,6431],{"class":262},[115,68813,6444],{"class":132},[115,68815,6437],{"class":132},[16,68817,3488],{},[106,68819,68821],{"className":108,"code":68820,"language":110,"meta":111,"style":111},"nslookup example.com\nnslookup www.example.com\n",[20,68822,68823,68829],{"__ignoreMap":111},[115,68824,68825,68827],{"class":117,"line":118},[115,68826,6451],{"class":262},[115,68828,6454],{"class":132},[115,68830,68831,68833],{"class":117,"line":136},[115,68832,6451],{"class":262},[115,68834,6696],{"class":132},[16,68836,68837],{},"If you want to test the app path through a server or proxy while sending the expected host header:",[106,68839,68841],{"className":108,"code":68840,"language":110,"meta":111,"style":111},"curl -I -H \"Host: example.com\" http:\u002F\u002FSERVER_IP\u002F\n",[20,68842,68843],{"__ignoreMap":111},[115,68844,68845,68847,68849,68851,68853],{"class":117,"line":118},[115,68846,2764],{"class":262},[115,68848,2767],{"class":202},[115,68850,3939],{"class":202},[115,68852,3942],{"class":132},[115,68854,68855],{"class":132}," http:\u002F\u002FSERVER_IP\u002F\n",[16,68857,68858],{},"This test only works if the reverse proxy is listening on that IP and port and is intended to receive direct traffic. If the site is only reachable through a load balancer or CDN, test against that layer instead.",[16,68860,42265],{},[63,68862,68863,68866,68869],{},[66,68864,68865],{},"the domain resolves to the correct server or load balancer",[66,68867,68868],{},"the request reaches the expected Nginx or Caddy instance",[66,68870,68871,68872,68874],{},"apex and ",[20,68873,4246],{}," both route correctly if both are supported",[11,68876,68878],{"id":68877},"step-5-reload-services-and-apply-changes-safely","Step 5 - Reload services and apply changes safely",[16,68880,68881],{},"Django settings changes do not apply until the app process reloads.",[52,68883,1946],{"id":14954},[16,68885,68886],{},"Restart:",[106,68888,68889],{"className":108,"code":3471,"language":110,"meta":111,"style":111},[20,68890,68891],{"__ignoreMap":111},[115,68892,68893,68895,68897,68899],{"class":117,"line":118},[115,68894,2001],{"class":262},[115,68896,3480],{"class":132},[115,68898,3483],{"class":132},[115,68900,1987],{"class":132},[16,68902,26640],{},[106,68904,68905],{"className":108,"code":51005,"language":110,"meta":111,"style":111},[20,68906,68907],{"__ignoreMap":111},[115,68908,68909,68911,68913,68915],{"class":117,"line":118},[115,68910,2001],{"class":262},[115,68912,3480],{"class":132},[115,68914,1984],{"class":132},[115,68916,1987],{"class":132},[16,68918,12724],{},[106,68920,68922],{"className":108,"code":68921,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn -n 50 --no-pager\n",[20,68923,68924],{"__ignoreMap":111},[115,68925,68926,68928,68930,68932,68934,68936,68938],{"class":117,"line":118},[115,68927,2001],{"class":262},[115,68929,5030],{"class":132},[115,68931,2788],{"class":202},[115,68933,2791],{"class":132},[115,68935,2794],{"class":202},[115,68937,15523],{"class":202},[115,68939,2800],{"class":202},[52,68941,68943],{"id":68942},"uvicorn-under-systemd","Uvicorn under systemd",[106,68945,68947],{"className":108,"code":68946,"language":110,"meta":111,"style":111},"sudo systemctl restart uvicorn\nsudo systemctl status uvicorn\n",[20,68948,68949,68959],{"__ignoreMap":111},[115,68950,68951,68953,68955,68957],{"class":117,"line":118},[115,68952,2001],{"class":262},[115,68954,3480],{"class":132},[115,68956,3483],{"class":132},[115,68958,12375],{"class":132},[115,68960,68961,68963,68965,68967],{"class":117,"line":136},[115,68962,2001],{"class":262},[115,68964,3480],{"class":132},[115,68966,1984],{"class":132},[115,68968,12375],{"class":132},[52,68970,4669],{"id":4776},[16,68972,68973],{},"If you changed environment values:",[106,68975,68976],{"className":108,"code":3491,"language":110,"meta":111,"style":111},[20,68977,68978],{"__ignoreMap":111},[115,68979,68980,68982,68984,68986,68988,68990],{"class":117,"line":118},[115,68981,3295],{"class":262},[115,68983,3298],{"class":132},[115,68985,3502],{"class":132},[115,68987,1019],{"class":202},[115,68989,3507],{"class":202},[115,68991,3510],{"class":132},[16,68993,43213],{},[106,68995,68996],{"className":108,"code":3288,"language":110,"meta":111,"style":111},[20,68997,68998],{"__ignoreMap":111},[115,68999,69000,69002,69004,69006,69008],{"class":117,"line":118},[115,69001,3295],{"class":262},[115,69003,3298],{"class":132},[115,69005,3301],{"class":132},[115,69007,3304],{"class":132},[115,69009,3307],{"class":202},[16,69011,17389],{},[63,69013,69015,69021,69027],{"className":69014},[48613],[66,69016,69018,69020],{"className":69017},[48617],[48619,69019],{"disabled":309,"type":48621}," app process restarted successfully",[66,69022,69024,69026],{"className":69023},[48617],[48619,69025],{"disabled":309,"type":48621}," no startup error from settings parsing",[66,69028,69030,69032],{"className":69029},[48617],[48619,69031],{"disabled":309,"type":48621}," proxy is still serving traffic after reload",[11,69034,69036],{"id":69035},"step-6-validate-the-fix-end-to-end","Step 6 - Validate the fix end to end",[16,69038,69039],{},"Test the public domain:",[106,69041,69043],{"className":108,"code":69042,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\ncurl -I https:\u002F\u002Fwww.example.com\n",[20,69044,69045,69053],{"__ignoreMap":111},[115,69046,69047,69049,69051],{"class":117,"line":118},[115,69048,2764],{"class":262},[115,69050,2767],{"class":202},[115,69052,2770],{"class":132},[115,69054,69055,69057,69059],{"class":117,"line":136},[115,69056,2764],{"class":262},[115,69058,2767],{"class":202},[115,69060,69061],{"class":132}," https:\u002F\u002Fwww.example.com\n",[16,69063,69064],{},"If relevant, also test the proxy or edge layer with an explicit host header:",[106,69066,69067],{"className":108,"code":68840,"language":110,"meta":111,"style":111},[20,69068,69069],{"__ignoreMap":111},[115,69070,69071,69073,69075,69077,69079],{"class":117,"line":118},[115,69072,2764],{"class":262},[115,69074,2767],{"class":202},[115,69076,3939],{"class":202},[115,69078,3942],{"class":132},[115,69080,68855],{"class":132},[16,69082,69083],{},"Then verify in a browser over HTTPS.",[16,69085,69086],{},"Finally, confirm the bad requests stopped appearing:",[106,69088,69089],{"className":108,"code":59564,"language":110,"meta":111,"style":111},[20,69090,69091],{"__ignoreMap":111},[115,69092,69093,69095,69097,69099,69101,69103,69105],{"class":117,"line":118},[115,69094,2001],{"class":262},[115,69096,5030],{"class":132},[115,69098,2788],{"class":202},[115,69100,2791],{"class":132},[115,69102,2794],{"class":202},[115,69104,2797],{"class":202},[115,69106,2800],{"class":202},[16,69108,69109,69110,69112],{},"You should also confirm unexpected hosts are still rejected. That is part of a correct ",[20,69111,2719],{}," Django production setup.",[11,69114,1321],{"id":1320},[16,69116,69117,69118,69120],{},"Django validates the request host to prevent host header attacks. In production, that means every valid public hostname must be declared in ",[20,69119,2719],{},", and your reverse proxy must pass that hostname through correctly.",[16,69122,69123],{},"This is why a Django app may work locally but fail behind Nginx or Gunicorn: local development often bypasses the exact production request path. Once a proxy, load balancer, or container runtime is introduced, the final host seen by Django can change.",[16,69125,69126,69128],{},[20,69127,2719],{}," should stay restrictive. It is better to list the exact domains you serve than to weaken host validation globally.",[16,69130,69131,69132,69134,69135,69137],{},"Also note that ",[20,69133,2725],{}," is separate. If forms or admin POST requests fail after fixing ",[20,69136,46086],{},", you may also need:",[106,69139,69140],{"className":2369,"code":3567,"language":1114,"meta":111,"style":111},[20,69141,69142,69150,69156,69162],{"__ignoreMap":111},[115,69143,69144,69146,69148],{"class":117,"line":118},[115,69145,2725],{"class":202},[115,69147,2380],{"class":121},[115,69149,3540],{"class":125},[115,69151,69152,69154],{"class":117,"line":136},[115,69153,3582],{"class":132},[115,69155,3354],{"class":125},[115,69157,69158,69160],{"class":117,"line":149},[115,69159,3589],{"class":132},[115,69161,3354],{"class":125},[115,69163,69164],{"class":117,"line":162},[115,69165,2552],{"class":125},[16,69167,69168,69169,69171],{},"That does not replace ",[20,69170,2719],{},"; it solves a different validation layer.",[11,69173,10095],{"id":10094},[52,69175,69177],{"id":69176},"running-behind-a-load-balancer-or-cdn","Running behind a load balancer or CDN",[16,69179,69180],{},"The public hostname must still be the one Django receives, directly or through correct proxy forwarding. Check each layer:",[63,69182,69183,69186,69189,69194],{},[66,69184,69185],{},"CDN or load balancer hostname rules",[66,69187,69188],{},"reverse proxy forwarding behavior",[66,69190,69191,69192],{},"Django ",[20,69193,2719],{},[66,69195,69196],{},"secure proxy header behavior if HTTPS terminates before Django",[16,69198,69199,69200,69202,69203,69205],{},"If one layer serves ",[20,69201,4373],{}," but Django only allows ",[20,69204,3145],{},", the request will still fail.",[52,69207,69209],{"id":69208},"docker-compose-and-stale-environment-values","Docker Compose and stale environment values",[16,69211,69212,69213,69215,69216,69218],{},"Changing ",[20,69214,191],{}," or Compose config does not always update a running container. Recreate the service after changing ",[20,69217,45407],{},", then confirm the new environment is actually active.",[52,69220,69222],{"id":69221},"kubernetes-ingress-hostname-mismatch","Kubernetes ingress hostname mismatch",[16,69224,69225,69226,69202,69228,69230,69231,69233],{},"If ingress serves ",[20,69227,4373],{},[20,69229,3145],{},", you will still get ",[20,69232,46086],{},". Match ingress host rules to Django settings.",[52,69235,69237],{"id":69236},"requests-hitting-the-server-by-ip-address","Requests hitting the server by IP address",[16,69239,69240,69241,69244,69245,69247],{},"If users or probes access ",[20,69242,69243],{},"http:\u002F\u002FSERVER_IP",", Django will reject that unless the IP is in ",[20,69246,2719],{},". Usually it is better to fix the caller to use the real hostname.",[52,69249,69251],{"id":69250},"multi-tenant-or-wildcard-subdomains","Multi-tenant or wildcard subdomains",[16,69253,69254,69255,69257],{},"These need more careful design than a simple static ",[20,69256,2719],{}," list. Validate exactly which host patterns are necessary before widening host acceptance.",[52,69259,69261],{"id":69260},"secure-proxy-header-note","Secure proxy header note",[16,69263,69264,69265,69267,69268,69270],{},"If your reverse proxy terminates TLS and forwards plain HTTP to Django, ",[20,69266,3203],{}," on the proxy side should match ",[20,69269,2377],{}," on the Django side:",[106,69272,69273],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,69274,69275],{"__ignoreMap":111},[115,69276,69277,69279,69281,69283,69285,69287,69289],{"class":117,"line":118},[115,69278,2377],{"class":202},[115,69280,2380],{"class":121},[115,69282,2383],{"class":125},[115,69284,2386],{"class":132},[115,69286,1153],{"class":125},[115,69288,2391],{"class":132},[115,69290,2394],{"class":125},[16,69292,69293],{},"Do not enable this unless requests reach Django through a trusted proxy path.",[52,69295,69297],{"id":69296},"when-manual-host-fixes-become-repetitive","When manual host fixes become repetitive",[16,69299,69300,69301,69303,69304,69306,69307,69309],{},"If you manage multiple Django services, standardize two things first: environment-driven ",[20,69302,2719],{}," parsing and a known-good proxy snippet that forwards ",[20,69305,3648],{}," correctly. CI\u002FCD can also run smoke tests against apex, ",[20,69308,4246],{},", and required subdomains after each deploy. That reduces configuration drift without changing the underlying security model.",[11,69311,1386],{"id":1385},[16,69313,32206,69314,211],{},[1395,69315,69316],{"href":4445},"What Is ALLOWED_HOSTS in Django and How Does It Work?",[16,69318,69319,69320,3146,69322,211],{},"If you are building the full stack around this fix, follow ",[1395,69321,55230],{"href":2985},[1395,69323,69324],{"href":3006},"How to Configure Django Production Settings Safely",[16,69326,69327,69328,211],{},"If the symptom is broader than host validation, use ",[1395,69329,69330],{"href":4445},"How to Debug 400 Bad Request Errors in Django Production",[11,69332,1420],{"id":1419},[11,69334,69336,69337,69339,69340,4474],{"id":69335},"why-do-i-still-get-disallowedhost-after-updating-allowed_hosts","Why do I still get ",[20,69338,46086],{}," after updating ",[20,69341,2719],{},[16,69343,69344],{},"Usually because the running app did not reload, the wrong settings file is active, or the proxy is forwarding a different host than the one you added. Check logs again and verify the exact host Django sees.",[11,69346,69348,69349,4474],{"id":69347},"should-i-add-my-server-ip-address-to-allowed_hosts","Should I add my server IP address to ",[20,69350,2719],{},[16,69352,69353],{},"Only if the application is intentionally accessed by IP address. In most production setups, users should access the app by domain, and health checks should do the same if possible.",[11,69355,11553,69357,3146,69359,4474],{"id":69356},"what-is-the-difference-between-allowed_hosts-and-csrf_trusted_origins",[20,69358,2719],{},[20,69360,2725],{},[16,69362,69363,69365,69366,69368],{},[20,69364,2719],{}," controls which request hosts Django accepts. ",[20,69367,2725],{}," controls which origins are trusted for unsafe requests like POST. They solve different problems.",[11,69370,69372,69373,69375],{"id":69371},"can-nginx-or-a-load-balancer-cause-django-disallowedhost-errors","Can Nginx or a load balancer cause Django ",[20,69374,46086],{}," errors?",[16,69377,69378,69379,69381],{},"Yes. If the proxy does not forward the original ",[20,69380,3648],{}," header, or if traffic reaches the app with an unexpected hostname, Django may reject it even when your domain looks correct externally.",[11,69383,69385,69386,39121],{"id":69384},"is-it-safe-to-set-allowed_hosts-in-production","Is it safe to set ",[20,69387,41912],{},[16,69389,69390],{},"Generally no. It disables Django's normal host validation and increases exposure to host header abuse. Use explicit hostnames unless you have a specific, reviewed reason not to.",[1485,69392,69393],{},"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 .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 .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":69395},[69396,69397,69398,69399,69400,69402,69406,69407,69412,69413,69414,69423,69424,69425,69427,69429,69431,69433],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":68160,"depth":136,"text":68161},{"id":68282,"depth":136,"text":69401},"Step 2 - Configure ALLOWED_HOSTS correctly",{"id":68531,"depth":136,"text":68532,"children":69403},[69404,69405],{"id":2156,"depth":149,"text":1647},{"id":23953,"depth":149,"text":24665},{"id":68787,"depth":136,"text":68788},{"id":68877,"depth":136,"text":68878,"children":69408},[69409,69410,69411],{"id":14954,"depth":149,"text":1946},{"id":68942,"depth":149,"text":68943},{"id":4776,"depth":149,"text":4669},{"id":69035,"depth":136,"text":69036},{"id":1320,"depth":136,"text":1321},{"id":10094,"depth":136,"text":10095,"children":69415},[69416,69417,69418,69419,69420,69421,69422],{"id":69176,"depth":149,"text":69177},{"id":69208,"depth":149,"text":69209},{"id":69221,"depth":149,"text":69222},{"id":69236,"depth":149,"text":69237},{"id":69250,"depth":149,"text":69251},{"id":69260,"depth":149,"text":69261},{"id":69296,"depth":149,"text":69297},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420},{"id":69335,"depth":136,"text":69426},"Why do I still get DisallowedHost after updating ALLOWED_HOSTS?",{"id":69347,"depth":136,"text":69428},"Should I add my server IP address to ALLOWED_HOSTS?",{"id":69356,"depth":136,"text":69430},"What is the difference between ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS?",{"id":69371,"depth":136,"text":69432},"Can Nginx or a load balancer cause Django DisallowedHost errors?",{"id":69384,"depth":136,"text":69434},"Is it safe to set ALLOWED_HOSTS = ['*'] in production?","A DisallowedHost error in Django production means Django rejected the incoming Host header. This is a security check, not a random app failure.",{},"\u002Ffix-disallowedhost-django-production",[4551,4418,4455],{"title":4446,"description":69435},[1557],"fix-disallowedhost-django-production",[1557],"h94ICNRe4jVU09kvDWKFKf2doUdR06D0a-DfzTBLm9Y",{"id":69445,"title":69446,"body":69447,"category":1541,"description":71131,"difficulty":23035,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":71132,"navigation":309,"path":71133,"priority":18004,"related":71134,"role":23035,"section":1554,"seo":71136,"stack":71137,"stem":71138,"tags":71139,"type":1561,"__hash__":71140},"articles\u002Fload-test-django-before-deploy.md","How to Load Test a Django App Before Release",{"type":8,"value":69448,"toc":71095},[69449,69451,69457,69460,69464,69471,69486,69490,69493,69513,69517,69524,69527,69529,69563,69565,69569,69572,69626,69629,69641,69643,69682,69685,69688,69692,69695,69712,69715,69751,69754,69756,69767,69771,69774,69794,69797,69811,69814,69818,69832,69836,69969,69971,70006,70009,70012,70274,70278,70382,70384,70398,70402,70423,70426,70430,70433,70436,70480,70483,70503,70506,70529,70532,70536,70540,70543,70546,70607,70609,70650,70653,70656,70660,70663,70725,70728,70748,70751,70770,70775,70779,70782,70802,70809,70812,70816,70819,70889,70892,70896,70899,70901,70904,70934,70943,70949,70951,71001,71003,71011,71022,71029,71036,71038,71042,71045,71049,71052,71056,71059,71063,71065,71082,71085,71089,71092],[11,69450,14],{"id":13},[16,69452,69453,69454,211],{},"Pre-release load testing answers a practical deployment question: ",[1226,69455,69456],{},"will this Django release survive real traffic on your actual production stack",[16,69458,69459],{},"A Django app can look fine in manual staging checks and still fail after release because of worker saturation, slow queries, connection exhaustion, misconfigured static files, or proxy timeouts. These failures often appear only when requests arrive concurrently and repeatedly.",[52,69461,69463],{"id":69462},"why-django-load-testing-before-release-matters","Why Django load testing before release matters",[16,69465,69466,69467,69470],{},"If you want reliable releases, test the release candidate on a ",[1226,69468,69469],{},"production-like staging environment"," with the same major components you use in production:",[63,69472,69473,69476,69478,69480,69483],{},[66,69474,69475],{},"reverse proxy such as Nginx or Caddy",[66,69477,1650],{},[66,69479,1773],{},[66,69481,69482],{},"Redis if used for cache, sessions, or background work",[66,69484,69485],{},"realistic environment variables and app settings",[52,69487,69489],{"id":69488},"what-can-go-wrong-if-you-skip-pre-release-load-testing","What can go wrong if you skip pre-release load testing",[16,69491,69492],{},"Common failures include:",[63,69494,69495,69498,69501,69504,69507,69510],{},[66,69496,69497],{},"Gunicorn workers timing out under moderate concurrency",[66,69499,69500],{},"PostgreSQL connections maxing out",[66,69502,69503],{},"one expensive endpoint blocking the release",[66,69505,69506],{},"static files accidentally served by Django instead of Nginx",[66,69508,69509],{},"login flows failing because of session or CSRF handling under load",[66,69511,69512],{},"a migration making a hot query slower at launch data volume",[52,69514,69516],{"id":69515},"what-this-guide-will-and-will-not-cover","What this guide will and will not cover",[16,69518,69519,69520,69523],{},"This guide covers a practical process for ",[1226,69521,69522],{},"load testing a Django app before release",", using staging, realistic traffic profiles, and basic bottleneck analysis.",[16,69525,69526],{},"It does not cover internet-scale benchmarking or vendor-specific observability platforms.",[11,69528,30],{"id":29},[1173,69530,69531,69539,69545,69551,69557],{},[66,69532,69533,69538],{},[1226,69534,69535,69536],{},"Test staging, not ",[20,69537,63454],{},". Use the same reverse proxy, app server, database, and cache pattern as production.",[66,69540,69541,69544],{},[1226,69542,69543],{},"Define pass\u002Ffail thresholds before running the test",". At minimum: p95 latency, error rate, and throughput targets.",[66,69546,69547,69550],{},[1226,69548,69549],{},"Simulate critical user flows",", not only the homepage.",[66,69552,69553,69556],{},[1226,69554,69555],{},"Monitor app, proxy, database, and cache together"," while the test runs.",[66,69558,69559,69562],{},[1226,69560,69561],{},"Do not release"," if migrations, worker settings, proxy behavior, or connection limits fail under expected traffic.",[11,69564,43],{"id":42},[52,69566,69568],{"id":69567},"_1-verify-the-staging-stack-before-testing","1. Verify the staging stack before testing",[16,69570,69571],{},"Use the exact release candidate you plan to deploy.",[106,69573,69575],{"className":108,"code":69574,"language":110,"meta":111,"style":111},"python manage.py check --deploy\npython manage.py showmigrations\n# If supported by your Django version:\npython manage.py migrate --plan\nsystemctl status gunicorn\nsystemctl status nginx\n",[20,69576,69577,69587,69595,69600,69610,69618],{"__ignoreMap":111},[115,69578,69579,69581,69583,69585],{"class":117,"line":118},[115,69580,1114],{"class":262},[115,69582,1117],{"class":132},[115,69584,1814],{"class":132},[115,69586,1817],{"class":202},[115,69588,69589,69591,69593],{"class":117,"line":136},[115,69590,1114],{"class":262},[115,69592,1117],{"class":132},[115,69594,1129],{"class":132},[115,69596,69597],{"class":117,"line":149},[115,69598,69599],{"class":3861},"# If supported by your Django version:\n",[115,69601,69602,69604,69606,69608],{"class":117,"line":162},[115,69603,1114],{"class":262},[115,69605,1117],{"class":132},[115,69607,1826],{"class":132},[115,69609,1829],{"class":202},[115,69611,69612,69614,69616],{"class":117,"line":175},[115,69613,1981],{"class":262},[115,69615,1984],{"class":132},[115,69617,1987],{"class":132},[115,69619,69620,69622,69624],{"class":117,"line":350},[115,69621,1981],{"class":262},[115,69623,1984],{"class":132},[115,69625,1996],{"class":132},[16,69627,69628],{},"Container-based alternative:",[106,69630,69631],{"className":108,"code":26643,"language":110,"meta":111,"style":111},[20,69632,69633],{"__ignoreMap":111},[115,69634,69635,69637,69639],{"class":117,"line":118},[115,69636,3295],{"class":262},[115,69638,3298],{"class":132},[115,69640,4790],{"class":132},[16,69642,17389],{},[63,69644,69645,69648,69651,69654,69657,69664,69669,69674,69679],{},[66,69646,69647],{},"Django system checks complete without critical deployment warnings",[66,69649,69650],{},"the migration state matches what you expect",[66,69652,69653],{},"Gunicorn and Nginx are running",[66,69655,69656],{},"staging is isolated from production services",[66,69658,69659,42293,69661,69663],{},[20,69660,7350],{},[20,69662,3364],{}," in staging",[66,69665,69666,69668],{},[20,69667,2719],{}," matches the staging hostname(s)",[66,69670,69671,69673],{},[20,69672,2725],{}," is set correctly if the staging host or scheme requires it",[66,69675,69676,69678],{},[20,69677,2377],{}," is configured correctly when TLS is terminated at the proxy",[66,69680,69681],{},"static files are actually served by Nginx, Caddy, or object storage if that is how production works",[16,69683,69684],{},"If you find config drift between staging and production, stop and fix that first. Load test results from a non-matching environment are weak release evidence.",[16,69686,69687],{},"Also apply the same rule to secrets and environment configuration as to data: use staging-specific secrets and credentials, not production secrets copied into staging.",[52,69689,69691],{"id":69690},"_2-seed-realistic-data","2. Seed realistic data",[16,69693,69694],{},"Load tests are only useful if the database size and cache behavior resemble production.",[106,69696,69698],{"className":108,"code":69697,"language":110,"meta":111,"style":111},"python manage.py loaddata sample-production-like-data.json\n",[20,69699,69700],{"__ignoreMap":111},[115,69701,69702,69704,69706,69709],{"class":117,"line":118},[115,69703,1114],{"class":262},[115,69705,1117],{"class":132},[115,69707,69708],{"class":132}," loaddata",[115,69710,69711],{"class":132}," sample-production-like-data.json\n",[16,69713,69714],{},"Check row counts in PostgreSQL:",[106,69716,69718],{"className":108,"code":69717,"language":110,"meta":111,"style":111},"psql \"$DATABASE_URL\" -c \"SELECT COUNT(*) FROM auth_user;\"\npsql \"$DATABASE_URL\" -c \"SELECT COUNT(*) FROM orders_order;\"\n",[20,69719,69720,69736],{"__ignoreMap":111},[115,69721,69722,69724,69726,69729,69731,69733],{"class":117,"line":118},[115,69723,835],{"class":262},[115,69725,325],{"class":132},[115,69727,69728],{"class":125},"$DATABASE_URL",[115,69730,331],{"class":132},[115,69732,1024],{"class":202},[115,69734,69735],{"class":132}," \"SELECT COUNT(*) FROM auth_user;\"\n",[115,69737,69738,69740,69742,69744,69746,69748],{"class":117,"line":136},[115,69739,835],{"class":262},[115,69741,325],{"class":132},[115,69743,69728],{"class":125},[115,69745,331],{"class":132},[115,69747,1024],{"class":202},[115,69749,69750],{"class":132}," \"SELECT COUNT(*) FROM orders_order;\"\n",[16,69752,69753],{},"Use sanitized data only. Do not copy production secrets or personal data into staging.",[16,69755,17389],{},[63,69757,69758,69761,69764],{},[66,69759,69760],{},"key tables have realistic row counts",[66,69762,69763],{},"test accounts exist",[66,69765,69766],{},"critical flows can be exercised manually before automation",[52,69768,69770],{"id":69769},"_3-define-the-traffic-profile","3. Define the traffic profile",[16,69772,69773],{},"Before running tools, write down:",[63,69775,69776,69779,69782,69785,69788,69791],{},[66,69777,69778],{},"critical user journeys",[66,69780,69781],{},"target requests per second",[66,69783,69784],{},"target concurrency",[66,69786,69787],{},"ramp rate",[66,69789,69790],{},"test duration",[66,69792,69793],{},"pass\u002Ffail thresholds",[16,69795,69796],{},"A simple release validation profile might look like:",[63,69798,69799,69802,69805,69808],{},[66,69800,69801],{},"baseline: 20 concurrent users for 5 minutes",[66,69803,69804],{},"peak: 50 concurrent users for 10 minutes",[66,69806,69807],{},"spike: ramp from 10 to 100 users over 2 minutes",[66,69809,69810],{},"pass threshold: p95 under 800ms, error rate under 1%",[16,69812,69813],{},"This matters because “the site felt fast” is not a release gate.",[52,69815,69817],{"id":69816},"_4-choose-a-load-testing-tool","4. Choose a load testing tool",[16,69819,55,69820,69823,69824,69827,69828,69831],{},[1226,69821,69822],{},"Locust"," when you need realistic Django user flows with login, sessions, and multiple endpoints. Use ",[1226,69825,69826],{},"k6"," when you want scripted scenarios with clear thresholds. Use ",[1226,69829,69830],{},"hey"," only for single-endpoint smoke benchmarking.",[1850,69833,69835],{"id":69834},"locust-example","Locust example",[106,69837,69839],{"className":2369,"code":69838,"language":1114,"meta":111,"style":111},"from locust import HttpUser, task, between\n\nclass DjangoUser(HttpUser):\n    wait_time = between(1, 3)\n\n    @task(3)\n    def homepage(self):\n        self.client.get(\"\u002F\")\n\n    @task(1)\n    def dashboard(self):\n        self.client.get(\"\u002Fdashboard\u002F\", name=\"\u002Fdashboard\u002F\")\n",[20,69840,69841,69853,69857,69871,69889,69893,69904,69914,69927,69931,69941,69950],{"__ignoreMap":111},[115,69842,69843,69845,69848,69850],{"class":117,"line":118},[115,69844,5621],{"class":121},[115,69846,69847],{"class":125}," locust ",[115,69849,5613],{"class":121},[115,69851,69852],{"class":125}," HttpUser, task, between\n",[115,69854,69855],{"class":117,"line":136},[115,69856,310],{"emptyLinePlaceholder":309},[115,69858,69859,69861,69864,69866,69869],{"class":117,"line":149},[115,69860,37139],{"class":121},[115,69862,69863],{"class":262}," DjangoUser",[115,69865,37145],{"class":125},[115,69867,69868],{"class":262},"HttpUser",[115,69870,37156],{"class":125},[115,69872,69873,69876,69878,69881,69883,69885,69887],{"class":117,"line":162},[115,69874,69875],{"class":125},"    wait_time ",[115,69877,129],{"class":121},[115,69879,69880],{"class":125}," between(",[115,69882,3351],{"class":202},[115,69884,1153],{"class":125},[115,69886,3094],{"class":202},[115,69888,2394],{"class":125},[115,69890,69891],{"class":117,"line":175},[115,69892,310],{"emptyLinePlaceholder":309},[115,69894,69895,69898,69900,69902],{"class":117,"line":350},[115,69896,69897],{"class":262},"    @task",[115,69899,37145],{"class":125},[115,69901,3094],{"class":202},[115,69903,2394],{"class":125},[115,69905,69906,69908,69911],{"class":117,"line":365},[115,69907,37205],{"class":121},[115,69909,69910],{"class":262}," homepage",[115,69912,69913],{"class":125},"(self):\n",[115,69915,69916,69919,69922,69925],{"class":117,"line":380},[115,69917,69918],{"class":202},"        self",[115,69920,69921],{"class":125},".client.get(",[115,69923,69924],{"class":132},"\"\u002F\"",[115,69926,2394],{"class":125},[115,69928,69929],{"class":117,"line":487},[115,69930,310],{"emptyLinePlaceholder":309},[115,69932,69933,69935,69937,69939],{"class":117,"line":2095},[115,69934,69897],{"class":262},[115,69936,37145],{"class":125},[115,69938,3351],{"class":202},[115,69940,2394],{"class":125},[115,69942,69943,69945,69948],{"class":117,"line":2104},[115,69944,37205],{"class":121},[115,69946,69947],{"class":262}," dashboard",[115,69949,69913],{"class":125},[115,69951,69952,69954,69956,69959,69961,69963,69965,69967],{"class":117,"line":2113},[115,69953,69918],{"class":202},[115,69955,69921],{"class":125},[115,69957,69958],{"class":132},"\"\u002Fdashboard\u002F\"",[115,69960,1153],{"class":125},[115,69962,20820],{"class":5680},[115,69964,129],{"class":121},[115,69966,69958],{"class":132},[115,69968,2394],{"class":125},[16,69970,21136],{},[106,69972,69974],{"className":108,"code":69973,"language":110,"meta":111,"style":111},"locust -f locustfile.py --headless -u 50 -r 5 -t 5m --host=https:\u002F\u002Fstaging.example.com\n",[20,69975,69976],{"__ignoreMap":111},[115,69977,69978,69981,69983,69986,69989,69991,69993,69995,69998,70000,70003],{"class":117,"line":118},[115,69979,69980],{"class":262},"locust",[115,69982,2777],{"class":202},[115,69984,69985],{"class":132}," locustfile.py",[115,69987,69988],{"class":202}," --headless",[115,69990,2788],{"class":202},[115,69992,15523],{"class":202},[115,69994,12350],{"class":202},[115,69996,69997],{"class":202}," 5",[115,69999,3909],{"class":202},[115,70001,70002],{"class":132}," 5m",[115,70004,70005],{"class":202}," --host=https:\u002F\u002Fstaging.example.com\n",[16,70007,70008],{},"For authenticated flows, log in first and preserve the session. If your login form uses CSRF protection, fetch the login page and submit the CSRF token rather than bypassing it.",[16,70010,70011],{},"Minimal Locust pattern for login with CSRF:",[106,70013,70015],{"className":2369,"code":70014,"language":1114,"meta":111,"style":111},"from bs4 import BeautifulSoup\nfrom locust import HttpUser, task, between\n\nclass AuthenticatedDjangoUser(HttpUser):\n    wait_time = between(1, 3)\n\n    def on_start(self):\n        response = self.client.get(\"\u002Faccounts\u002Flogin\u002F\")\n        soup = BeautifulSoup(response.text, \"html.parser\")\n        csrf = soup.find(\"input\", {\"name\": \"csrfmiddlewaretoken\"})[\"value\"]\n\n        self.client.post(\n            \"\u002Faccounts\u002Flogin\u002F\",\n            data={\n                \"username\": \"testuser\",\n                \"password\": \"testpass\",\n                \"csrfmiddlewaretoken\": csrf,\n            },\n            headers={\"Referer\": f\"{self.host}\u002Faccounts\u002Flogin\u002F\"},\n        )\n\n    @task\n    def dashboard(self):\n        self.client.get(\"\u002Fdashboard\u002F\")\n",[20,70016,70017,70029,70039,70043,70056,70072,70076,70085,70101,70116,70148,70152,70159,70166,70175,70187,70199,70207,70212,70243,70247,70251,70256,70264],{"__ignoreMap":111},[115,70018,70019,70021,70024,70026],{"class":117,"line":118},[115,70020,5621],{"class":121},[115,70022,70023],{"class":125}," bs4 ",[115,70025,5613],{"class":121},[115,70027,70028],{"class":125}," BeautifulSoup\n",[115,70030,70031,70033,70035,70037],{"class":117,"line":136},[115,70032,5621],{"class":121},[115,70034,69847],{"class":125},[115,70036,5613],{"class":121},[115,70038,69852],{"class":125},[115,70040,70041],{"class":117,"line":149},[115,70042,310],{"emptyLinePlaceholder":309},[115,70044,70045,70047,70050,70052,70054],{"class":117,"line":162},[115,70046,37139],{"class":121},[115,70048,70049],{"class":262}," AuthenticatedDjangoUser",[115,70051,37145],{"class":125},[115,70053,69868],{"class":262},[115,70055,37156],{"class":125},[115,70057,70058,70060,70062,70064,70066,70068,70070],{"class":117,"line":175},[115,70059,69875],{"class":125},[115,70061,129],{"class":121},[115,70063,69880],{"class":125},[115,70065,3351],{"class":202},[115,70067,1153],{"class":125},[115,70069,3094],{"class":202},[115,70071,2394],{"class":125},[115,70073,70074],{"class":117,"line":350},[115,70075,310],{"emptyLinePlaceholder":309},[115,70077,70078,70080,70083],{"class":117,"line":365},[115,70079,37205],{"class":121},[115,70081,70082],{"class":262}," on_start",[115,70084,69913],{"class":125},[115,70086,70087,70090,70092,70094,70096,70099],{"class":117,"line":380},[115,70088,70089],{"class":125},"        response ",[115,70091,129],{"class":121},[115,70093,37258],{"class":202},[115,70095,69921],{"class":125},[115,70097,70098],{"class":132},"\"\u002Faccounts\u002Flogin\u002F\"",[115,70100,2394],{"class":125},[115,70102,70103,70106,70108,70111,70114],{"class":117,"line":487},[115,70104,70105],{"class":125},"        soup ",[115,70107,129],{"class":121},[115,70109,70110],{"class":125}," BeautifulSoup(response.text, ",[115,70112,70113],{"class":132},"\"html.parser\"",[115,70115,2394],{"class":125},[115,70117,70118,70121,70123,70126,70129,70132,70135,70137,70140,70143,70146],{"class":117,"line":2095},[115,70119,70120],{"class":125},"        csrf ",[115,70122,129],{"class":121},[115,70124,70125],{"class":125}," soup.find(",[115,70127,70128],{"class":132},"\"input\"",[115,70130,70131],{"class":125},", {",[115,70133,70134],{"class":132},"\"name\"",[115,70136,2513],{"class":125},[115,70138,70139],{"class":132},"\"csrfmiddlewaretoken\"",[115,70141,70142],{"class":125},"})[",[115,70144,70145],{"class":132},"\"value\"",[115,70147,2552],{"class":125},[115,70149,70150],{"class":117,"line":2104},[115,70151,310],{"emptyLinePlaceholder":309},[115,70153,70154,70156],{"class":117,"line":2113},[115,70155,69918],{"class":202},[115,70157,70158],{"class":125},".client.post(\n",[115,70160,70161,70164],{"class":117,"line":2122},[115,70162,70163],{"class":132},"            \"\u002Faccounts\u002Flogin\u002F\"",[115,70165,3354],{"class":125},[115,70167,70168,70171,70173],{"class":117,"line":2131},[115,70169,70170],{"class":5680},"            data",[115,70172,129],{"class":121},[115,70174,2220],{"class":125},[115,70176,70177,70180,70182,70185],{"class":117,"line":2136},[115,70178,70179],{"class":132},"                \"username\"",[115,70181,2513],{"class":125},[115,70183,70184],{"class":132},"\"testuser\"",[115,70186,3354],{"class":125},[115,70188,70189,70192,70194,70197],{"class":117,"line":2142},[115,70190,70191],{"class":132},"                \"password\"",[115,70193,2513],{"class":125},[115,70195,70196],{"class":132},"\"testpass\"",[115,70198,3354],{"class":125},[115,70200,70201,70204],{"class":117,"line":2273},[115,70202,70203],{"class":132},"                \"csrfmiddlewaretoken\"",[115,70205,70206],{"class":125},": csrf,\n",[115,70208,70209],{"class":117,"line":2282},[115,70210,70211],{"class":125},"            },\n",[115,70213,70214,70217,70219,70221,70224,70226,70228,70230,70233,70236,70238,70241],{"class":117,"line":2291},[115,70215,70216],{"class":5680},"            headers",[115,70218,129],{"class":121},[115,70220,65899],{"class":125},[115,70222,70223],{"class":132},"\"Referer\"",[115,70225,2513],{"class":125},[115,70227,36099],{"class":121},[115,70229,331],{"class":132},[115,70231,70232],{"class":202},"{self",[115,70234,70235],{"class":125},".host",[115,70237,65904],{"class":202},[115,70239,70240],{"class":132},"\u002Faccounts\u002Flogin\u002F\"",[115,70242,9142],{"class":125},[115,70244,70245],{"class":117,"line":2299},[115,70246,54187],{"class":125},[115,70248,70249],{"class":117,"line":2307},[115,70250,310],{"emptyLinePlaceholder":309},[115,70252,70253],{"class":117,"line":2315},[115,70254,70255],{"class":262},"    @task\n",[115,70257,70258,70260,70262],{"class":117,"line":2320},[115,70259,37205],{"class":121},[115,70261,69947],{"class":262},[115,70263,69913],{"class":125},[115,70265,70266,70268,70270,70272],{"class":117,"line":7083},[115,70267,69918],{"class":202},[115,70269,69921],{"class":125},[115,70271,69958],{"class":132},[115,70273,2394],{"class":125},[1850,70275,70277],{"id":70276},"k6-example","k6 example",[106,70279,70283],{"className":70280,"code":70281,"language":70282,"meta":111,"style":111},"language-javascript shiki shiki-themes github-light github-dark","import http from 'k6\u002Fhttp';\nimport { sleep, check } from 'k6';\n\nexport const options = {\n  stages: [\n    { duration: '2m', target: 20 },\n    { duration: '5m', target: 50 },\n    { duration: '2m', target: 0 },\n  ],\n  thresholds: {\n    http_req_failed: ['rate\u003C0.01'],\n    http_req_duration: ['p(95)\u003C800'],\n  },\n};\n\nexport default function () {\n  const res = http.get('https:\u002F\u002Fstaging.example.com\u002F');\n  check(res, { 'status is 200': (r) => r.status === 200 });\n  sleep(1);\n}\n","javascript",[20,70284,70285,70290,70295,70299,70304,70309,70314,70319,70324,70329,70334,70339,70344,70349,70354,70358,70363,70368,70373,70378],{"__ignoreMap":111},[115,70286,70287],{"class":117,"line":118},[115,70288,70289],{},"import http from 'k6\u002Fhttp';\n",[115,70291,70292],{"class":117,"line":136},[115,70293,70294],{},"import { sleep, check } from 'k6';\n",[115,70296,70297],{"class":117,"line":149},[115,70298,310],{"emptyLinePlaceholder":309},[115,70300,70301],{"class":117,"line":162},[115,70302,70303],{},"export const options = {\n",[115,70305,70306],{"class":117,"line":175},[115,70307,70308],{},"  stages: [\n",[115,70310,70311],{"class":117,"line":350},[115,70312,70313],{},"    { duration: '2m', target: 20 },\n",[115,70315,70316],{"class":117,"line":365},[115,70317,70318],{},"    { duration: '5m', target: 50 },\n",[115,70320,70321],{"class":117,"line":380},[115,70322,70323],{},"    { duration: '2m', target: 0 },\n",[115,70325,70326],{"class":117,"line":487},[115,70327,70328],{},"  ],\n",[115,70330,70331],{"class":117,"line":2095},[115,70332,70333],{},"  thresholds: {\n",[115,70335,70336],{"class":117,"line":2104},[115,70337,70338],{},"    http_req_failed: ['rate\u003C0.01'],\n",[115,70340,70341],{"class":117,"line":2113},[115,70342,70343],{},"    http_req_duration: ['p(95)\u003C800'],\n",[115,70345,70346],{"class":117,"line":2122},[115,70347,70348],{},"  },\n",[115,70350,70351],{"class":117,"line":2131},[115,70352,70353],{},"};\n",[115,70355,70356],{"class":117,"line":2136},[115,70357,310],{"emptyLinePlaceholder":309},[115,70359,70360],{"class":117,"line":2142},[115,70361,70362],{},"export default function () {\n",[115,70364,70365],{"class":117,"line":2273},[115,70366,70367],{},"  const res = http.get('https:\u002F\u002Fstaging.example.com\u002F');\n",[115,70369,70370],{"class":117,"line":2282},[115,70371,70372],{},"  check(res, { 'status is 200': (r) => r.status === 200 });\n",[115,70374,70375],{"class":117,"line":2291},[115,70376,70377],{},"  sleep(1);\n",[115,70379,70380],{"class":117,"line":2299},[115,70381,2323],{},[16,70383,21136],{},[106,70385,70387],{"className":108,"code":70386,"language":110,"meta":111,"style":111},"k6 run script.js\n",[20,70388,70389],{"__ignoreMap":111},[115,70390,70391,70393,70395],{"class":117,"line":118},[115,70392,69826],{"class":262},[115,70394,18889],{"class":132},[115,70396,70397],{"class":132}," script.js\n",[1850,70399,70401],{"id":70400},"lightweight-single-endpoint-check","Lightweight single-endpoint check",[106,70403,70405],{"className":108,"code":70404,"language":110,"meta":111,"style":111},"hey -n 1000 -c 20 https:\u002F\u002Fstaging.example.com\u002Fhealth\u002F\n",[20,70406,70407],{"__ignoreMap":111},[115,70408,70409,70411,70413,70416,70418,70420],{"class":117,"line":118},[115,70410,69830],{"class":262},[115,70412,2794],{"class":202},[115,70414,70415],{"class":202}," 1000",[115,70417,1024],{"class":202},[115,70419,38349],{"class":202},[115,70421,70422],{"class":132}," https:\u002F\u002Fstaging.example.com\u002Fhealth\u002F\n",[16,70424,70425],{},"Use this only to smoke test one endpoint, not to validate the release.",[52,70427,70429],{"id":70428},"_5-run-the-test-safely","5. Run the test safely",[16,70431,70432],{},"Start small, then increase concurrency gradually.",[16,70434,70435],{},"While the test runs, monitor the host and services:",[106,70437,70439],{"className":108,"code":70438,"language":110,"meta":111,"style":111},"htop\nvmstat 1\nss -s\njournalctl -u gunicorn -f\ntail -f \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,70440,70441,70446,70452,70459,70469],{"__ignoreMap":111},[115,70442,70443],{"class":117,"line":118},[115,70444,70445],{"class":262},"htop\n",[115,70447,70448,70450],{"class":117,"line":136},[115,70449,52254],{"class":262},[115,70451,8995],{"class":202},[115,70453,70454,70456],{"class":117,"line":149},[115,70455,6472],{"class":262},[115,70457,70458],{"class":202}," -s\n",[115,70460,70461,70463,70465,70467],{"class":117,"line":162},[115,70462,2785],{"class":262},[115,70464,2788],{"class":202},[115,70466,2791],{"class":132},[115,70468,36482],{"class":202},[115,70470,70471,70473,70475,70478],{"class":117,"line":175},[115,70472,36495],{"class":262},[115,70474,2777],{"class":202},[115,70476,70477],{"class":132}," \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log",[115,70479,13195],{"class":132},[16,70481,70482],{},"Check PostgreSQL activity:",[106,70484,70486],{"className":108,"code":70485,"language":110,"meta":111,"style":111},"psql \"$DATABASE_URL\" -c \"SELECT state, count(*) FROM pg_stat_activity GROUP BY state;\"\n",[20,70487,70488],{"__ignoreMap":111},[115,70489,70490,70492,70494,70496,70498,70500],{"class":117,"line":118},[115,70491,835],{"class":262},[115,70493,325],{"class":132},[115,70495,69728],{"class":125},[115,70497,331],{"class":132},[115,70499,1024],{"class":202},[115,70501,70502],{"class":132}," \"SELECT state, count(*) FROM pg_stat_activity GROUP BY state;\"\n",[16,70504,70505],{},"What to watch:",[63,70507,70508,70511,70514,70517,70520,70523,70526],{},[66,70509,70510],{},"CPU saturation",[66,70512,70513],{},"memory pressure or swapping",[66,70515,70516],{},"rising error rates",[66,70518,70519],{},"request timeouts",[66,70521,70522],{},"database connection growth",[66,70524,70525],{},"repeated 499, 502, 504, or other 5xx responses",[66,70527,70528],{},"Redis saturation if cache or session traffic is involved",[16,70530,70531],{},"Do not point test traffic at production-only dependencies. If your app calls third-party APIs, stub them, disable them, or use safe test credentials and rate limits.",[52,70533,70535],{"id":70534},"_6-tune-common-bottlenecks","6. Tune common bottlenecks",[1850,70537,70539],{"id":70538},"gunicorn-worker-settings","Gunicorn worker settings",[16,70541,70542],{},"A common issue is too few workers or timeouts that do not match real request behavior.",[16,70544,70545],{},"Example systemd snippet:",[106,70547,70549],{"className":2026,"code":70548,"language":2028,"meta":111,"style":111},"[Service]\nEnvironment=\"DJANGO_SETTINGS_MODULE=config.settings.production\"\nExecStart=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application \\\n  --bind 127.0.0.1:8000 \\\n  --workers 4 \\\n  --timeout 30 \\\n  --access-logfile - \\\n  --error-logfile -\nRestart=on-failure\nRestartSec=5\n",[20,70550,70551,70555,70563,70570,70575,70580,70585,70590,70595,70601],{"__ignoreMap":111},[115,70552,70553],{"class":117,"line":118},[115,70554,2060],{"class":262},[115,70556,70557,70559,70561],{"class":117,"line":136},[115,70558,36637],{"class":121},[115,70560,129],{"class":125},[115,70562,36642],{"class":132},[115,70564,70565,70567],{"class":117,"line":149},[115,70566,2107],{"class":121},[115,70568,70569],{"class":125},"=\u002Fsrv\u002Fapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application \\\n",[115,70571,70572],{"class":117,"line":162},[115,70573,70574],{"class":125},"  --bind 127.0.0.1:8000 \\\n",[115,70576,70577],{"class":117,"line":175},[115,70578,70579],{"class":125},"  --workers 4 \\\n",[115,70581,70582],{"class":117,"line":350},[115,70583,70584],{"class":125},"  --timeout 30 \\\n",[115,70586,70587],{"class":117,"line":365},[115,70588,70589],{"class":125},"  --access-logfile - \\\n",[115,70591,70592],{"class":117,"line":380},[115,70593,70594],{"class":125},"  --error-logfile -\n",[115,70596,70597,70599],{"class":117,"line":487},[115,70598,2116],{"class":121},[115,70600,2119],{"class":125},[115,70602,70603,70605],{"class":117,"line":2095},[115,70604,2125],{"class":121},[115,70606,2128],{"class":125},[16,70608,35222],{},[106,70610,70612],{"className":108,"code":70611,"language":110,"meta":111,"style":111},"systemctl daemon-reload\nsystemctl restart gunicorn\nsystemctl status gunicorn\njournalctl -u gunicorn -n 50 --no-pager\n",[20,70613,70614,70620,70628,70636],{"__ignoreMap":111},[115,70615,70616,70618],{"class":117,"line":118},[115,70617,1981],{"class":262},[115,70619,4984],{"class":132},[115,70621,70622,70624,70626],{"class":117,"line":136},[115,70623,1981],{"class":262},[115,70625,3483],{"class":132},[115,70627,1987],{"class":132},[115,70629,70630,70632,70634],{"class":117,"line":149},[115,70631,1981],{"class":262},[115,70633,1984],{"class":132},[115,70635,1987],{"class":132},[115,70637,70638,70640,70642,70644,70646,70648],{"class":117,"line":162},[115,70639,2785],{"class":262},[115,70641,2788],{"class":202},[115,70643,2791],{"class":132},[115,70645,2794],{"class":202},[115,70647,15523],{"class":202},[115,70649,2800],{"class":202},[16,70651,70652],{},"Retest after each change. Do not tune multiple variables at once if you want useful comparisons.",[16,70654,70655],{},"Rollback note: if a worker change causes instability, restore the previous unit file, reload systemd, restart Gunicorn, and confirm the service starts cleanly before re-running the test.",[1850,70657,70659],{"id":70658},"nginx-proxy-alignment","Nginx proxy alignment",[16,70661,70662],{},"Make sure proxy settings do not create artificial failures.",[106,70664,70666],{"className":2154,"code":70665,"language":2156,"meta":111,"style":111},"location \u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_set_header X-Forwarded-Host $host;\n    proxy_read_timeout 30s;\n}\n",[20,70667,70668,70676,70682,70688,70694,70700,70706,70712,70721],{"__ignoreMap":111},[115,70669,70670,70672,70674],{"class":117,"line":118},[115,70671,7128],{"class":121},[115,70673,2268],{"class":262},[115,70675,2220],{"class":125},[115,70677,70678,70680],{"class":117,"line":136},[115,70679,7137],{"class":121},[115,70681,3748],{"class":125},[115,70683,70684,70686],{"class":117,"line":149},[115,70685,7144],{"class":121},[115,70687,2288],{"class":125},[115,70689,70690,70692],{"class":117,"line":162},[115,70691,7144],{"class":121},[115,70693,3767],{"class":125},[115,70695,70696,70698],{"class":117,"line":175},[115,70697,7144],{"class":121},[115,70699,2312],{"class":125},[115,70701,70702,70704],{"class":117,"line":350},[115,70703,7144],{"class":121},[115,70705,2304],{"class":125},[115,70707,70708,70710],{"class":117,"line":365},[115,70709,7144],{"class":121},[115,70711,2296],{"class":125},[115,70713,70714,70716,70719],{"class":117,"line":380},[115,70715,52980],{"class":121},[115,70717,70718],{"class":202},"30s",[115,70720,3811],{"class":125},[115,70722,70723],{"class":117,"line":487},[115,70724,2323],{"class":125},[16,70726,70727],{},"If staging terminates TLS at the proxy, verify Django trusts the forwarded scheme correctly before testing login, redirects, secure cookies, or CSRF behavior.",[106,70729,70730],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,70731,70732],{"__ignoreMap":111},[115,70733,70734,70736,70738,70740,70742,70744,70746],{"class":117,"line":118},[115,70735,2377],{"class":202},[115,70737,2380],{"class":121},[115,70739,2383],{"class":125},[115,70741,2386],{"class":132},[115,70743,1153],{"class":125},[115,70745,2391],{"class":132},[115,70747,2394],{"class":125},[16,70749,70750],{},"Validate and reload safely:",[106,70752,70754],{"className":108,"code":70753,"language":110,"meta":111,"style":111},"nginx -t\nsystemctl reload nginx\n",[20,70755,70756,70762],{"__ignoreMap":111},[115,70757,70758,70760],{"class":117,"line":118},[115,70759,2156],{"class":262},[115,70761,4282],{"class":202},[115,70763,70764,70766,70768],{"class":117,"line":136},[115,70765,1981],{"class":262},[115,70767,3919],{"class":132},[115,70769,1996],{"class":132},[16,70771,70772,70773,68784],{},"Rollback note: if reload introduces errors, restore the previous config, validate with ",[20,70774,7611],{},[1850,70776,70778],{"id":70777},"slow-queries-and-connection-pressure","Slow queries and connection pressure",[16,70780,70781],{},"If database-heavy endpoints degrade first, inspect the slow query path.",[106,70783,70785],{"className":108,"code":70784,"language":110,"meta":111,"style":111},"psql \"$DATABASE_URL\" -c \"SELECT count(*) FROM pg_stat_activity;\"\n",[20,70786,70787],{"__ignoreMap":111},[115,70788,70789,70791,70793,70795,70797,70799],{"class":117,"line":118},[115,70790,835],{"class":262},[115,70792,325],{"class":132},[115,70794,69728],{"class":125},[115,70796,331],{"class":132},[115,70798,1024],{"class":202},[115,70800,70801],{"class":132}," \"SELECT count(*) FROM pg_stat_activity;\"\n",[16,70803,70804,70805,70808],{},"For a known slow query, run ",[20,70806,70807],{},"EXPLAIN ANALYZE"," in staging before adding indexes or changing ORM behavior. Re-test after any schema or query change.",[16,70810,70811],{},"Do not assume migration rollback is a safe recovery path; some schema changes are not trivially reversible, so test migrations separately before load validation.",[52,70813,70815],{"id":70814},"_7-record-the-result-and-gate-the-release","7. Record the result and gate the release",[16,70817,70818],{},"Keep a simple decision record for each tested release:",[106,70820,70824],{"className":70821,"code":70822,"language":70823,"meta":111,"style":111},"language-markdown shiki shiki-themes github-light github-dark","- Date: 2026-04-24\n- Commit SHA: abc1234\n- Image tag: web-abc1234\n- Gunicorn workers: 4\n- Nginx config version: 2026-04-24\n- Database size note: production-like seed v3\n- Test scenarios: baseline, peak, spike\n- Result: PASS\n- Notes: dashboard endpoint improved after index added\n","markdown",[20,70825,70826,70833,70840,70847,70854,70861,70868,70875,70882],{"__ignoreMap":111},[115,70827,70828,70830],{"class":117,"line":118},[115,70829,36579],{"class":5680},[115,70831,70832],{"class":125}," Date: 2026-04-24\n",[115,70834,70835,70837],{"class":117,"line":136},[115,70836,36579],{"class":5680},[115,70838,70839],{"class":125}," Commit SHA: abc1234\n",[115,70841,70842,70844],{"class":117,"line":149},[115,70843,36579],{"class":5680},[115,70845,70846],{"class":125}," Image tag: web-abc1234\n",[115,70848,70849,70851],{"class":117,"line":162},[115,70850,36579],{"class":5680},[115,70852,70853],{"class":125}," Gunicorn workers: 4\n",[115,70855,70856,70858],{"class":117,"line":175},[115,70857,36579],{"class":5680},[115,70859,70860],{"class":125}," Nginx config version: 2026-04-24\n",[115,70862,70863,70865],{"class":117,"line":350},[115,70864,36579],{"class":5680},[115,70866,70867],{"class":125}," Database size note: production-like seed v3\n",[115,70869,70870,70872],{"class":117,"line":365},[115,70871,36579],{"class":5680},[115,70873,70874],{"class":125}," Test scenarios: baseline, peak, spike\n",[115,70876,70877,70879],{"class":117,"line":380},[115,70878,36579],{"class":5680},[115,70880,70881],{"class":125}," Result: PASS\n",[115,70883,70884,70886],{"class":117,"line":487},[115,70885,36579],{"class":5680},[115,70887,70888],{"class":125}," Notes: dashboard endpoint improved after index added\n",[16,70890,70891],{},"Do not release based on anecdotal testing. Release only if the measured result meets your thresholds.",[52,70893,70895],{"id":70894},"when-to-script-this-process","When to script this process",[16,70897,70898],{},"Once you repeat this before every release, the manual steps become good candidates for automation. Teams usually script staging checks, data seeding, standard Locust or k6 runs, metric capture, and release reports first. That keeps test inputs consistent and makes regressions easier to spot.",[11,70900,1321],{"id":1320},[16,70902,70903],{},"This process works because it tests the real failure points in a Django deployment chain:",[63,70905,70906,70912,70917,70922,70928],{},[66,70907,70908,70911],{},[1226,70909,70910],{},"reverse proxy"," limits and timeout behavior",[66,70913,70914,70916],{},[1226,70915,1650],{}," worker capacity",[66,70918,70919,70921],{},[1226,70920,53618],{}," query cost and connection exhaustion",[66,70923,70924,70927],{},[1226,70925,70926],{},"cache and sessions"," under concurrency",[66,70929,70930,70933],{},[1226,70931,70932],{},"application code"," paths that only break under repeated parallel requests",[16,70935,70936,70937,70939,70940,70942],{},"Testing ",[20,70938,63454],{}," is not useful for release validation because it does not represent production behavior. Likewise, hitting only ",[20,70941,44219],{}," tells you almost nothing about authenticated views, write-heavy endpoints, or pages that run expensive queries.",[16,70944,70945,70946,70948],{},"Locust is usually the better fit when you need session-backed Django behavior and multi-step flows. k6 is strong when you want explicit threshold-based execution in CI. Small tools like ",[20,70947,69830],{}," are useful for a quick benchmark of one route, but they do not replace scenario-based testing.",[11,70950,1337],{"id":1336},[63,70952,70953,70959,70965,70971,70977,70983,70989,70995],{},[66,70954,70955,70958],{},[1226,70956,70957],{},"Docker vs non-Docker",": if production runs in containers, stage the same way. Host-level and container-level CPU or memory limits can change results.",[66,70960,70961,70964],{},[1226,70962,70963],{},"ASGI and async views",": if you use Uvicorn or another ASGI server, include async endpoints in tests and verify upstream timeout alignment.",[66,70966,70967,70970],{},[1226,70968,70969],{},"Static and media",": do not benchmark static asset delivery through Django if production serves them through Nginx, Caddy, or object storage.",[66,70972,70973,70976],{},[1226,70974,70975],{},"Migrations first",": run and validate migrations before the main performance test. A schema change can alter query plans significantly.",[66,70978,70979,70982],{},[1226,70980,70981],{},"Third-party APIs",": avoid generating accidental real traffic or charges during test runs.",[66,70984,70985,70988],{},[1226,70986,70987],{},"Large uploads and downloads",": test them separately if they are business-critical. They distort results for standard page traffic.",[66,70990,70991,70994],{},[1226,70992,70993],{},"TLS and proxy headers",": if staging terminates TLS differently from production, document that difference because it can affect request handling and security assumptions.",[66,70996,70997,71000],{},[1226,70998,70999],{},"Recovery planning",": if a release fails load validation because of schema, proxy, or worker changes, treat rollback as a controlled deployment task, not an assumption. App rollback is usually simpler than schema rollback.",[11,71002,1386],{"id":1385},[16,71004,71005,71006,211],{},"If you need the environment setup first, read ",[1226,71007,71008],{},[1395,71009,71010],{"href":2999},"What Is a Production-Like Django Staging Environment",[16,71012,71013,71014,3146,71018,211],{},"For the stack itself, see ",[1226,71015,71016],{},[1395,71017,2986],{"href":2985},[1226,71019,71020],{},[1395,71021,8039],{"href":8038},[16,71023,71024,71025,211],{},"If you are using a different reverse proxy, see ",[1226,71026,71027],{},[1395,71028,8046],{"href":8045},[16,71030,71031,71032,211],{},"For validation before release, use ",[1226,71033,71034],{},[1395,71035,3000],{"href":2999},[11,71037,1420],{"id":1419},[52,71039,71041],{"id":71040},"should-i-load-test-a-django-app-against-staging-or-production","Should I load test a Django app against staging or production?",[16,71043,71044],{},"Use staging for pre-release validation. It should mirror production closely, but remain isolated from real users and production-only dependencies. Production load testing is a separate operational decision and carries more risk.",[52,71046,71048],{"id":71047},"what-is-a-reasonable-concurrency-target-for-django-load-testing-before-release","What is a reasonable concurrency target for Django load testing before release?",[16,71050,71051],{},"Use expected launch traffic plus headroom. Start with current or forecast peak concurrent users, then add a safety margin. If you do not have historical data, test baseline, peak, and short spike scenarios rather than picking one arbitrary number.",[52,71053,71055],{"id":71054},"how-do-i-load-test-authenticated-django-views-that-use-sessions-and-csrf","How do I load test authenticated Django views that use sessions and CSRF?",[16,71057,71058],{},"Use a tool that supports multi-step flows, such as Locust or k6. Fetch the login page first, capture the CSRF token, submit the login form, and then reuse the authenticated session or cookies for later requests.",[52,71060,71062],{"id":71061},"which-metrics-should-block-a-release-if-they-degrade-under-load","Which metrics should block a release if they degrade under load?",[16,71064,10908],{},[63,71066,71067,71070,71073,71076,71079],{},[66,71068,71069],{},"p95 latency",[66,71071,71072],{},"error rate",[66,71074,71075],{},"throughput at target concurrency",[66,71077,71078],{},"CPU and memory pressure",[66,71080,71081],{},"database connections and slow query behavior",[16,71083,71084],{},"If one critical endpoint fails under expected traffic, that alone can justify blocking the release.",[52,71086,71088],{"id":71087},"how-often-should-i-repeat-load-testing-in-the-release-process","How often should I repeat load testing in the release process?",[16,71090,71091],{},"Repeat it for meaningful changes to traffic-sensitive code, infrastructure, query behavior, caching, worker settings, or schema. At minimum, run it before important releases and after any fix intended to improve performance.",[1485,71093,71094],{},"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 .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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":71096},[71097,71102,71103,71121,71122,71123,71124],{"id":13,"depth":136,"text":14,"children":71098},[71099,71100,71101],{"id":69462,"depth":149,"text":69463},{"id":69488,"depth":149,"text":69489},{"id":69515,"depth":149,"text":69516},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":71104},[71105,71106,71107,71108,71113,71114,71119,71120],{"id":69567,"depth":149,"text":69568},{"id":69690,"depth":149,"text":69691},{"id":69769,"depth":149,"text":69770},{"id":69816,"depth":149,"text":69817,"children":71109},[71110,71111,71112],{"id":69834,"depth":162,"text":69835},{"id":70276,"depth":162,"text":70277},{"id":70400,"depth":162,"text":70401},{"id":70428,"depth":149,"text":70429},{"id":70534,"depth":149,"text":70535,"children":71115},[71116,71117,71118],{"id":70538,"depth":162,"text":70539},{"id":70658,"depth":162,"text":70659},{"id":70777,"depth":162,"text":70778},{"id":70814,"depth":149,"text":70815},{"id":70894,"depth":149,"text":70895},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":71125},[71126,71127,71128,71129,71130],{"id":71040,"depth":149,"text":71041},{"id":71047,"depth":149,"text":71048},{"id":71054,"depth":149,"text":71055},{"id":71061,"depth":149,"text":71062},{"id":71087,"depth":149,"text":71088},"Pre-release load testing answers a practical deployment question: will this Django release survive real traffic on your actual production stack.",{},"\u002Fload-test-django-before-deploy",[37876,71135,1415],"\u002Foptimize\u002Fterraform-django-server-baseline",{"title":69446,"description":71131},[1557,69980],"load-test-django-before-deploy",[1557,69980],"DPNM2Jtqjuv0YVFoSFkBtFGO_-jNRnkPYn2PgWDhvNc",{"id":71142,"title":71143,"body":71144,"category":1541,"description":71150,"difficulty":23035,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":72533,"navigation":309,"path":72534,"priority":20256,"related":72535,"role":23035,"section":1554,"seo":72536,"stack":72537,"stem":72538,"tags":72539,"type":1561,"__hash__":72540},"articles\u002Foptimize-gunicorn-workers-django.md","How to Optimize Gunicorn Workers for Django",{"type":8,"value":71145,"toc":72497},[71146,71148,71151,71154,71157,71183,71186,71188,71191,71224,71227,71260,71262,71266,71272,71275,71289,71292,71309,71312,71316,71319,71322,71383,71386,71407,71410,71414,71419,71425,71428,71431,71445,71448,71467,71470,71500,71503,71507,71513,71519,71525,71531,71533,71563,71566,71576,71579,71583,71586,71590,71595,71743,71746,71805,71808,71814,71818,71821,71824,71941,71950,71953,71975,71978,72039,72042,72059,72062,72066,72069,72075,72096,72099,72120,72123,72144,72147,72151,72185,72189,72192,72195,72223,72226,72246,72252,72255,72258,72272,72274,72347,72350,72364,72367,72369,72372,72386,72389,72393,72399,72401,72426,72428,72431,72452,72454,72458,72464,72472,72480,72484,72487,72491,72494],[11,71147,14],{"id":13},[16,71149,71150],{},"If you want to optimize Gunicorn workers for Django, the main production risk is getting concurrency wrong.",[16,71152,71153],{},"Too few workers means requests queue behind busy processes, latency rises, and Nginx or Caddy may start returning 502 or 504 errors during traffic spikes. Too many workers means each Gunicorn process competes for CPU and RAM, which can increase context switching, trigger swapping, and make deployments unstable.",[16,71155,71156],{},"There is no universal best Gunicorn worker count for Django. The right settings depend on:",[63,71158,71159,71162,71165,71168,71171,71174,71177,71180],{},[66,71160,71161],{},"available CPU cores",[66,71163,71164],{},"available RAM",[66,71166,71167],{},"request profile",[66,71169,71170],{},"database latency",[66,71172,71173],{},"external API calls",[66,71175,71176],{},"worker class",[66,71178,71179],{},"timeout behavior",[66,71181,71182],{},"PostgreSQL connection limits",[16,71184,71185],{},"The goal is to choose a conservative starting point, test under realistic load, and change one variable at a time.",[11,71187,30],{"id":29},[16,71189,71190],{},"To tune Gunicorn workers for Django production safely:",[1173,71192,71193,71198,71201,71206,71216,71221],{},[66,71194,37788,71195,71197],{},[20,71196,52856],{}," workers and a small worker count based on CPU.",[66,71199,71200],{},"Measure memory usage per worker before increasing concurrency.",[66,71202,55,71203,71205],{},[20,71204,52862],{}," only if the app is moderately I\u002FO-bound and you understand the thread tradeoffs.",[66,71207,53552,71208,1153,71210,20346,71212,71215],{},[20,71209,47977],{},[20,71211,52492],{},[20,71213,71214],{},"max_requests"," aligned with safe reloads and worker recycling.",[66,71217,71218,71219,211],{},"Test with realistic endpoints, not only ",[20,71220,32941],{},[66,71222,71223],{},"Validate the Gunicorn config before reload, and keep a rollback config ready.",[16,71225,71226],{},"For many standard Django apps, a good first test is:",[63,71228,71229,71234,71240,71245,71250,71255],{},[66,71230,71231],{},[20,71232,71233],{},"worker_class = \"sync\"",[66,71235,71236,71239],{},[20,71237,71238],{},"workers = 2 * CPU_CORES + 1"," as a starting point, not a rule",[66,71241,71242],{},[20,71243,71244],{},"timeout = 30",[66,71246,71247],{},[20,71248,71249],{},"graceful_timeout = 30",[66,71251,71252],{},[20,71253,71254],{},"max_requests = 1000",[66,71256,71257],{},[20,71258,71259],{},"max_requests_jitter = 100",[11,71261,43],{"id":42},[52,71263,71265],{"id":71264},"_1-understand-what-gunicorn-workers-do","1. Understand what Gunicorn workers do",[16,71267,71268,71269,71271],{},"Gunicorn uses worker processes to handle requests. With the default ",[20,71270,52856],{}," class, each worker handles one request at a time.",[16,71273,71274],{},"In a typical Django deployment:",[63,71276,71277,71280,71283,71286],{},[66,71278,71279],{},"Nginx or Caddy accepts client traffic",[66,71281,71282],{},"the reverse proxy forwards dynamic requests to Gunicorn",[66,71284,71285],{},"a Gunicorn worker runs Django code",[66,71287,71288],{},"Django may query PostgreSQL, Redis, or external services",[16,71290,71291],{},"This means worker tuning affects:",[63,71293,71294,71297,71300,71303,71306],{},[66,71295,71296],{},"request concurrency",[66,71298,71299],{},"latency under load",[66,71301,71302],{},"memory footprint",[66,71304,71305],{},"database connection usage",[66,71307,71308],{},"behavior during deploy reloads",[16,71310,71311],{},"If requests are mostly CPU-bound, more workers than available CPU can make performance worse. If requests spend meaningful time waiting on DB or network I\u002FO, a threaded model may help.",[52,71313,71315],{"id":71314},"_2-collect-baseline-metrics-before-changing-settings","2. Collect baseline metrics before changing settings",[16,71317,71318],{},"Before changing Gunicorn workers for Django, capture current behavior.",[16,71320,71321],{},"Check system state:",[106,71323,71325],{"className":108,"code":71324,"language":110,"meta":111,"style":111},"free -m\ntop\nss -ltnp | grep 8000\nsystemctl status gunicorn\njournalctl -u gunicorn -n 100 --no-pager\nps aux | grep gunicorn\n",[20,71326,71327,71333,71337,71349,71357,71371],{"__ignoreMap":111},[115,71328,71329,71331],{"class":117,"line":118},[115,71330,52246],{"class":262},[115,71332,52249],{"class":202},[115,71334,71335],{"class":117,"line":136},[115,71336,52241],{"class":262},[115,71338,71339,71341,71343,71345,71347],{"class":117,"line":149},[115,71340,6472],{"class":262},[115,71342,52214],{"class":202},[115,71344,579],{"class":121},[115,71346,4838],{"class":262},[115,71348,23864],{"class":202},[115,71350,71351,71353,71355],{"class":117,"line":162},[115,71352,1981],{"class":262},[115,71354,1984],{"class":132},[115,71356,1987],{"class":132},[115,71358,71359,71361,71363,71365,71367,71369],{"class":117,"line":175},[115,71360,2785],{"class":262},[115,71362,2788],{"class":202},[115,71364,2791],{"class":132},[115,71366,2794],{"class":202},[115,71368,2797],{"class":202},[115,71370,2800],{"class":202},[115,71372,71373,71375,71377,71379,71381],{"class":117,"line":350},[115,71374,4830],{"class":262},[115,71376,4833],{"class":132},[115,71378,579],{"class":121},[115,71380,4838],{"class":262},[115,71382,1987],{"class":132},[16,71384,71385],{},"What to measure:",[63,71387,71388,71390,71393,71396,71398,71401,71404],{},[66,71389,70510],{},[66,71391,71392],{},"RAM usage before and after traffic",[66,71394,71395],{},"worker count actually running",[66,71397,71069],{},[66,71399,71400],{},"502 or 504 rate at the reverse proxy",[66,71402,71403],{},"worker timeout or restart messages in logs",[66,71405,71406],{},"PostgreSQL connection count",[16,71408,71409],{},"If CPU is low but latency is high, the bottleneck may be database or external I\u002FO rather than Gunicorn itself. Do not raise concurrency until you know where time is being spent.",[52,71411,71413],{"id":71412},"_3-choose-an-initial-gunicorn-worker-count-for-django","3. Choose an initial Gunicorn worker count for Django",[16,71415,43602,71416,71418],{},[20,71417,52856],{}," workers, start with the common baseline:",[106,71420,71423],{"className":71421,"code":71422,"language":247,"meta":111},[245],"workers = 2 * CPU_CORES + 1\n",[20,71424,71422],{"__ignoreMap":111},[16,71426,71427],{},"Use it as a first test only.",[16,71429,71430],{},"In practice, test fewer workers first when:",[63,71432,71433,71436,71439,71442],{},[66,71434,71435],{},"the server has limited RAM",[66,71437,71438],{},"each Django worker uses significant memory",[66,71440,71441],{},"PostgreSQL connection limits are tight",[66,71443,71444],{},"the app does heavy CPU work",[16,71446,71447],{},"A practical process:",[63,71449,71450,71458,71461,71464],{},[66,71451,71452,71453,4493,71455,71457],{},"start with ",[20,71454,35628],{},[20,71456,3094],{}," workers on a 1–2 vCPU VPS",[66,71459,71460],{},"test under load",[66,71462,71463],{},"measure memory per worker",[66,71465,71466],{},"increase gradually only if latency improves without memory pressure",[16,71468,71469],{},"Example starting points:",[63,71471,71472,71482,71491],{},[66,71473,71474,2957,71477,1153,71480],{},[1226,71475,71476],{},"1 vCPU \u002F 2 GB RAM:",[20,71478,71479],{},"workers = 2",[20,71481,71233],{},[66,71483,71484,2957,71487,71490],{},[1226,71485,71486],{},"2 vCPU \u002F 4 GB RAM:",[20,71488,71489],{},"workers = 3 to 5",", test both",[66,71492,71493,2957,71496,71499],{},[1226,71494,71495],{},"4 vCPU \u002F 8 GB RAM:",[20,71497,71498],{},"workers = 5 to 9",", depending on memory and DB behavior",[16,71501,71502],{},"If one worker uses 300 MB under real traffic, eight workers already consume roughly 2.4 GB before accounting for the OS, Nginx, PostgreSQL, Redis, and filesystem cache.",[52,71504,71506],{"id":71505},"_4-pick-the-right-worker-class","4. Pick the right worker class",[1850,71508,71510,71512],{"id":71509},"sync-workers-for-typical-django-apps",[20,71511,52856],{}," workers for typical Django apps",[16,71514,71515,71516,71518],{},"For most standard Django request\u002Fresponse apps, ",[20,71517,52856],{}," is the safest default. It is simple, predictable, and usually easier to debug.",[1850,71520,71522,71524],{"id":71521},"gthread-workers-for-moderate-io-bound-workloads",[20,71523,52862],{}," workers for moderate I\u002FO-bound workloads",[16,71526,71527,71528,71530],{},"If the app spends noticeable time waiting on the database or external APIs, ",[20,71529,52862],{}," can increase concurrency with fewer processes.",[16,71532,12414],{},[106,71534,71536],{"className":2369,"code":71535,"language":1114,"meta":111,"style":111},"worker_class = \"gthread\"\nthreads = 4\nworkers = 2\n",[20,71537,71538,71546,71555],{"__ignoreMap":111},[115,71539,71540,71542,71544],{"class":117,"line":118},[115,71541,52436],{"class":125},[115,71543,129],{"class":121},[115,71545,52441],{"class":132},[115,71547,71548,71550,71552],{"class":117,"line":136},[115,71549,52426],{"class":125},[115,71551,129],{"class":121},[115,71553,71554],{"class":202}," 4\n",[115,71556,71557,71559,71561],{"class":117,"line":149},[115,71558,52416],{"class":125},[115,71560,129],{"class":121},[115,71562,52431],{"class":202},[16,71564,71565],{},"Do not raise both workers and threads aggressively at the same time. Start small and watch memory, latency, and database pressure.",[1850,71567,71569,3146,71572,71575],{"id":71568},"gevent-and-eventlet-caveats",[20,71570,71571],{},"gevent",[20,71573,71574],{},"eventlet"," caveats",[16,71577,71578],{},"These models require more care around monkey patching and compatibility. For most Django deployments, they are not the first choice for straightforward production tuning.",[1850,71580,71582],{"id":71581},"when-asgi-and-uvicorn-are-a-better-fit","When ASGI and Uvicorn are a better fit",[16,71584,71585],{},"If your application depends heavily on async views, websockets, or long-lived connections, moving toward ASGI with Uvicorn may be better than trying to force high concurrency through Gunicorn tuning alone.",[52,71587,71589],{"id":71588},"_5-tune-the-gunicorn-settings-that-matter-most","5. Tune the Gunicorn settings that matter most",[16,71591,17097,71592,241],{},[20,71593,71594],{},"gunicorn.conf.py",[106,71596,71598],{"className":2369,"code":71597,"language":1114,"meta":111,"style":111},"bind = \"127.0.0.1:8000\"\n\nworkers = 3\nworker_class = \"sync\"\nthreads = 1\n\ntimeout = 30\ngraceful_timeout = 30\nkeepalive = 2\n\nmax_requests = 1000\nmax_requests_jitter = 100\n\npreload_app = False\nbacklog = 2048\n\naccesslog = \"\u002Fvar\u002Flog\u002Fgunicorn\u002Faccess.log\"\nerrorlog = \"\u002Fvar\u002Flog\u002Fgunicorn\u002Ferror.log\"\nloglevel = \"info\"\n",[20,71599,71600,71608,71612,71620,71629,71637,71641,71649,71657,71666,71670,71678,71686,71690,71699,71709,71713,71723,71733],{"__ignoreMap":111},[115,71601,71602,71604,71606],{"class":117,"line":118},[115,71603,52406],{"class":125},[115,71605,129],{"class":121},[115,71607,52411],{"class":132},[115,71609,71610],{"class":117,"line":136},[115,71611,310],{"emptyLinePlaceholder":309},[115,71613,71614,71616,71618],{"class":117,"line":149},[115,71615,52416],{"class":125},[115,71617,129],{"class":121},[115,71619,52421],{"class":202},[115,71621,71622,71624,71626],{"class":117,"line":162},[115,71623,52436],{"class":125},[115,71625,129],{"class":121},[115,71627,71628],{"class":132}," \"sync\"\n",[115,71630,71631,71633,71635],{"class":117,"line":175},[115,71632,52426],{"class":125},[115,71634,129],{"class":121},[115,71636,8995],{"class":202},[115,71638,71639],{"class":117,"line":350},[115,71640,310],{"emptyLinePlaceholder":309},[115,71642,71643,71645,71647],{"class":117,"line":365},[115,71644,52446],{"class":125},[115,71646,129],{"class":121},[115,71648,52451],{"class":202},[115,71650,71651,71653,71655],{"class":117,"line":380},[115,71652,52456],{"class":125},[115,71654,129],{"class":121},[115,71656,52451],{"class":202},[115,71658,71659,71662,71664],{"class":117,"line":487},[115,71660,71661],{"class":125},"keepalive ",[115,71663,129],{"class":121},[115,71665,52431],{"class":202},[115,71667,71668],{"class":117,"line":2095},[115,71669,310],{"emptyLinePlaceholder":309},[115,71671,71672,71674,71676],{"class":117,"line":2104},[115,71673,52465],{"class":125},[115,71675,129],{"class":121},[115,71677,52470],{"class":202},[115,71679,71680,71682,71684],{"class":117,"line":2113},[115,71681,52475],{"class":125},[115,71683,129],{"class":121},[115,71685,46680],{"class":202},[115,71687,71688],{"class":117,"line":2122},[115,71689,310],{"emptyLinePlaceholder":309},[115,71691,71692,71695,71697],{"class":117,"line":2131},[115,71693,71694],{"class":125},"preload_app ",[115,71696,129],{"class":121},[115,71698,7355],{"class":202},[115,71700,71701,71704,71706],{"class":117,"line":2136},[115,71702,71703],{"class":125},"backlog ",[115,71705,129],{"class":121},[115,71707,71708],{"class":202}," 2048\n",[115,71710,71711],{"class":117,"line":2142},[115,71712,310],{"emptyLinePlaceholder":309},[115,71714,71715,71718,71720],{"class":117,"line":2273},[115,71716,71717],{"class":125},"accesslog ",[115,71719,129],{"class":121},[115,71721,71722],{"class":132}," \"\u002Fvar\u002Flog\u002Fgunicorn\u002Faccess.log\"\n",[115,71724,71725,71728,71730],{"class":117,"line":2282},[115,71726,71727],{"class":125},"errorlog ",[115,71729,129],{"class":121},[115,71731,71732],{"class":132}," \"\u002Fvar\u002Flog\u002Fgunicorn\u002Ferror.log\"\n",[115,71734,71735,71738,71740],{"class":117,"line":2291},[115,71736,71737],{"class":125},"loglevel ",[115,71739,129],{"class":121},[115,71741,71742],{"class":132}," \"info\"\n",[16,71744,71745],{},"What matters:",[63,71747,71748,71753,71763,71768,71773,71779,71787,71793,71799],{},[66,71749,71750,71752],{},[1226,71751,52497],{},": main process concurrency control",[66,71754,71755,71757,71758,71760,71761],{},[1226,71756,52502],{},": useful with ",[20,71759,52862],{},"; not relevant for ",[20,71762,52856],{},[66,71764,71765,71767],{},[1226,71766,47977],{},": kill stuck workers after this many seconds",[66,71769,71770,71772],{},[1226,71771,52492],{},": time allowed for workers to finish during restart or reload",[66,71774,71775,71778],{},[1226,71776,71777],{},"keepalive",": low values are usually fine behind Nginx or Caddy",[66,71780,71781,3146,71783,71786],{},[1226,71782,71214],{},[1226,71784,71785],{},"max_requests_jitter",": recycle workers periodically to reduce the impact of gradual memory growth",[66,71788,71789,71792],{},[1226,71790,71791],{},"preload_app",": can reduce memory via copy-on-write in some cases, but increases startup sensitivity and needs careful testing",[66,71794,71795,71798],{},[1226,71796,71797],{},"bind",": usually localhost or a Unix socket behind a reverse proxy",[66,71800,71801,71804],{},[1226,71802,71803],{},"backlog",": pending connection queue size; not the first tuning lever, but it should not be left too small",[16,71806,71807],{},"Do not expose Gunicorn directly on a public interface unless you explicitly intend to.",[16,71809,71810,71811,71813],{},"Also keep secrets out of Gunicorn config. ",[20,71812,71594],{}," should contain runtime settings, not Django secrets or credentials.",[52,71815,71817],{"id":71816},"_6-apply-changes-safely-in-production","6. Apply changes safely in production",[16,71819,71820],{},"Use a dedicated config file and a non-root service user.",[16,71822,71823],{},"Example systemd unit excerpt:",[106,71825,71827],{"className":2026,"code":71826,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Gunicorn for Django\nAfter=network.target\n\n[Service]\nUser=django\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironment=\"DJANGO_SETTINGS_MODULE=config.settings.production\"\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --config \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py\nExecReload=\u002Fbin\u002Fkill -s HUP $MAINPID\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=45\nKillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\n",[20,71828,71829,71833,71840,71846,71850,71854,71860,71866,71872,71880,71886,71893,71900,71906,71912,71919,71927,71931,71935],{"__ignoreMap":111},[115,71830,71831],{"class":117,"line":118},[115,71832,2035],{"class":262},[115,71834,71835,71837],{"class":117,"line":136},[115,71836,2040],{"class":121},[115,71838,71839],{"class":125},"=Gunicorn for Django\n",[115,71841,71842,71844],{"class":117,"line":149},[115,71843,2048],{"class":121},[115,71845,2051],{"class":125},[115,71847,71848],{"class":117,"line":162},[115,71849,310],{"emptyLinePlaceholder":309},[115,71851,71852],{"class":117,"line":175},[115,71853,2060],{"class":262},[115,71855,71856,71858],{"class":117,"line":350},[115,71857,2065],{"class":121},[115,71859,2068],{"class":125},[115,71861,71862,71864],{"class":117,"line":365},[115,71863,2073],{"class":121},[115,71865,2076],{"class":125},[115,71867,71868,71870],{"class":117,"line":380},[115,71869,2081],{"class":121},[115,71871,4905],{"class":125},[115,71873,71874,71876,71878],{"class":117,"line":487},[115,71875,36637],{"class":121},[115,71877,129],{"class":125},[115,71879,36642],{"class":132},[115,71881,71882,71884],{"class":117,"line":2095},[115,71883,2089],{"class":121},[115,71885,4912],{"class":125},[115,71887,71888,71890],{"class":117,"line":2104},[115,71889,2107],{"class":121},[115,71891,71892],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --config \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py\n",[115,71894,71895,71897],{"class":117,"line":2113},[115,71896,21951],{"class":121},[115,71898,71899],{"class":125},"=\u002Fbin\u002Fkill -s HUP $MAINPID\n",[115,71901,71902,71904],{"class":117,"line":2122},[115,71903,2116],{"class":121},[115,71905,2119],{"class":125},[115,71907,71908,71910],{"class":117,"line":2131},[115,71909,2125],{"class":121},[115,71911,2128],{"class":125},[115,71913,71914,71916],{"class":117,"line":2136},[115,71915,9286],{"class":121},[115,71917,71918],{"class":125},"=45\n",[115,71920,71921,71924],{"class":117,"line":2142},[115,71922,71923],{"class":121},"KillMode",[115,71925,71926],{"class":125},"=mixed\n",[115,71928,71929],{"class":117,"line":2273},[115,71930,310],{"emptyLinePlaceholder":309},[115,71932,71933],{"class":117,"line":2282},[115,71934,2139],{"class":262},[115,71936,71937,71939],{"class":117,"line":2291},[115,71938,2145],{"class":121},[115,71940,2148],{"class":125},[16,71942,71943,71944,71946,71947,71949],{},"Store secrets like Django settings values in ",[20,71945,2089],{},", not directly in the unit when possible. Do not store secrets in ",[20,71948,71594],{},", in the systemd unit directly, or in version-controlled deployment configs.",[16,71951,71952],{},"Validate the config before reload:",[106,71954,71956],{"className":108,"code":71955,"language":110,"meta":111,"style":111},"sudo \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn --check-config --config \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py config.wsgi:application\n",[20,71957,71958],{"__ignoreMap":111},[115,71959,71960,71962,71965,71968,71970,71973],{"class":117,"line":118},[115,71961,2001],{"class":262},[115,71963,71964],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn",[115,71966,71967],{"class":202}," --check-config",[115,71969,24112],{"class":202},[115,71971,71972],{"class":132}," \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py",[115,71974,23611],{"class":132},[16,71976,71977],{},"Then reload and verify:",[106,71979,71981],{"className":108,"code":71980,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl reload gunicorn\nsudo systemctl status gunicorn\nsudo journalctl -u gunicorn -n 100 --no-pager\nps aux | grep gunicorn\n",[20,71982,71983,71991,72001,72011,72027],{"__ignoreMap":111},[115,71984,71985,71987,71989],{"class":117,"line":118},[115,71986,2001],{"class":262},[115,71988,3480],{"class":132},[115,71990,4984],{"class":132},[115,71992,71993,71995,71997,71999],{"class":117,"line":136},[115,71994,2001],{"class":262},[115,71996,3480],{"class":132},[115,71998,3919],{"class":132},[115,72000,1987],{"class":132},[115,72002,72003,72005,72007,72009],{"class":117,"line":149},[115,72004,2001],{"class":262},[115,72006,3480],{"class":132},[115,72008,1984],{"class":132},[115,72010,1987],{"class":132},[115,72012,72013,72015,72017,72019,72021,72023,72025],{"class":117,"line":162},[115,72014,2001],{"class":262},[115,72016,5030],{"class":132},[115,72018,2788],{"class":202},[115,72020,2791],{"class":132},[115,72022,2794],{"class":202},[115,72024,2797],{"class":202},[115,72026,2800],{"class":202},[115,72028,72029,72031,72033,72035,72037],{"class":117,"line":175},[115,72030,4830],{"class":262},[115,72032,4833],{"class":132},[115,72034,579],{"class":121},[115,72036,4838],{"class":262},[115,72038,1987],{"class":132},[16,72040,72041],{},"Check the listener:",[106,72043,72045],{"className":108,"code":72044,"language":110,"meta":111,"style":111},"ss -ltnp | grep 8000\n",[20,72046,72047],{"__ignoreMap":111},[115,72048,72049,72051,72053,72055,72057],{"class":117,"line":118},[115,72050,6472],{"class":262},[115,72052,52214],{"class":202},[115,72054,579],{"class":121},[115,72056,4838],{"class":262},[115,72058,23864],{"class":202},[16,72060,72061],{},"Then confirm the reverse proxy still passes traffic and health checks.",[52,72063,72065],{"id":72064},"_7-test-worker-settings-under-realistic-traffic","7. Test worker settings under realistic traffic",[16,72067,72068],{},"Use a dynamic endpoint that exercises templates, ORM, middleware, or authentication paths. Do not test only a trivial health endpoint.",[16,72070,72071,72072,241],{},"Example with ",[20,72073,72074],{},"wrk",[106,72076,72078],{"className":108,"code":72077,"language":110,"meta":111,"style":111},"wrk -t4 -c20 -d30s https:\u002F\u002Fexample.com\u002Fapp\u002Fdashboard\u002F\n",[20,72079,72080],{"__ignoreMap":111},[115,72081,72082,72084,72087,72090,72093],{"class":117,"line":118},[115,72083,72074],{"class":262},[115,72085,72086],{"class":202}," -t4",[115,72088,72089],{"class":202}," -c20",[115,72091,72092],{"class":202}," -d30s",[115,72094,72095],{"class":132}," https:\u002F\u002Fexample.com\u002Fapp\u002Fdashboard\u002F\n",[16,72097,72098],{},"Or with ApacheBench:",[106,72100,72102],{"className":108,"code":72101,"language":110,"meta":111,"style":111},"ab -n 500 -c 20 https:\u002F\u002Fexample.com\u002Fapp\u002Fdashboard\u002F\n",[20,72103,72104],{"__ignoreMap":111},[115,72105,72106,72109,72111,72114,72116,72118],{"class":117,"line":118},[115,72107,72108],{"class":262},"ab",[115,72110,2794],{"class":202},[115,72112,72113],{"class":202}," 500",[115,72115,1024],{"class":202},[115,72117,38349],{"class":202},[115,72119,72095],{"class":132},[16,72121,72122],{},"Watch:",[63,72124,72125,72127,72130,72133,72136,72138,72141],{},[66,72126,71069],{},[66,72128,72129],{},"throughput",[66,72131,72132],{},"CPU utilization",[66,72134,72135],{},"RAM growth",[66,72137,46308],{},[66,72139,72140],{},"Nginx or Caddy 502\u002F504 responses",[66,72142,72143],{},"PostgreSQL connections",[16,72145,72146],{},"If throughput rises but p95 latency worsens and memory jumps, the new setting is not necessarily better.",[52,72148,72150],{"id":72149},"_8-avoid-common-gunicorn-tuning-mistakes","8. Avoid common Gunicorn tuning mistakes",[63,72152,72153,72159,72165,72173,72179],{},[66,72154,72155,72158],{},[1226,72156,72157],{},"Oversubscribing CPU on small servers",": more workers can increase waiting, not reduce it.",[66,72160,72161,72164],{},[1226,72162,72163],{},"Ignoring database connection limits",": higher app concurrency can increase simultaneous database usage.",[66,72166,72167,72172],{},[1226,72168,53285,72169,72171],{},[20,72170,47977],{}," instead of fixing slow queries",": long timeouts hide problems and hold resources longer.",[66,72174,72175,72178],{},[1226,72176,72177],{},"Using too many threads with blocking code",": thread count is not free concurrency.",[66,72180,72181,72184],{},[1226,72182,72183],{},"Forgetting memory per worker",": worker count must fit alongside PostgreSQL, Redis, Nginx, and the OS.",[52,72186,72188],{"id":72187},"_9-security-and-reliability-notes","9. Security and reliability notes",[16,72190,72191],{},"Keep Gunicorn behind Nginx or Caddy. Let the reverse proxy handle TLS, public exposure, buffering, and header normalization.",[16,72193,72194],{},"Also make sure to:",[63,72196,72197,72200,72203,72206,72209,72212],{},[66,72198,72199],{},"run Gunicorn as a non-root user",[66,72201,72202],{},"rotate or manage logs so disks do not fill",[66,72204,72205],{},"align reverse proxy timeouts and health checks with Gunicorn restart and timeout behavior",[66,72207,72208],{},"make sure static and media files are handled by the web server or object storage, not Gunicorn",[66,72210,72211],{},"avoid running migrations as part of Gunicorn startup",[66,72213,72214,72215,72217,72218,20346,72220,72222],{},"ensure Django production settings are correct as well, including ",[20,72216,32431],{},", valid ",[20,72219,2719],{},[20,72221,2725],{}," where needed",[16,72224,72225],{},"If TLS terminates at Nginx or Caddy, make sure Django is proxy-aware:",[106,72227,72228],{"className":2369,"code":2370,"language":1114,"meta":111,"style":111},[20,72229,72230],{"__ignoreMap":111},[115,72231,72232,72234,72236,72238,72240,72242,72244],{"class":117,"line":118},[115,72233,2377],{"class":202},[115,72235,2380],{"class":121},[115,72237,2383],{"class":125},[115,72239,2386],{"class":132},[115,72241,1153],{"class":125},[115,72243,2391],{"class":132},[115,72245,2394],{"class":125},[16,72247,72248,72249,72251],{},"Make sure your reverse proxy forwards ",[20,72250,3203],{}," correctly, or Django may not detect HTTPS requests properly.",[52,72253,72254],{"id":19932},"10. Rollback and recovery",[16,72256,72257],{},"If a new Gunicorn worker setting causes worse performance:",[1173,72259,72260,72263,72266,72269],{},[66,72261,72262],{},"Restore the previous config.",[66,72264,72265],{},"Validate the restored config.",[66,72267,72268],{},"Reload Gunicorn.",[66,72270,72271],{},"Recheck latency, memory, and logs.",[16,72273,12414],{},[106,72275,72277],{"className":108,"code":72276,"language":110,"meta":111,"style":111},"sudo cp \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py.bak \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py\nsudo \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn --check-config --config \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py config.wsgi:application\nsudo systemctl reload gunicorn\nsudo systemctl status gunicorn\nsudo journalctl -u gunicorn -n 100 --no-pager\nfree -m\n",[20,72278,72279,72291,72305,72315,72325,72341],{"__ignoreMap":111},[115,72280,72281,72283,72285,72288],{"class":117,"line":118},[115,72282,2001],{"class":262},[115,72284,6516],{"class":132},[115,72286,72287],{"class":132}," \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py.bak",[115,72289,72290],{"class":132}," \u002Fetc\u002Fmyapp\u002Fgunicorn.conf.py\n",[115,72292,72293,72295,72297,72299,72301,72303],{"class":117,"line":136},[115,72294,2001],{"class":262},[115,72296,71964],{"class":132},[115,72298,71967],{"class":202},[115,72300,24112],{"class":202},[115,72302,71972],{"class":132},[115,72304,23611],{"class":132},[115,72306,72307,72309,72311,72313],{"class":117,"line":149},[115,72308,2001],{"class":262},[115,72310,3480],{"class":132},[115,72312,3919],{"class":132},[115,72314,1987],{"class":132},[115,72316,72317,72319,72321,72323],{"class":117,"line":162},[115,72318,2001],{"class":262},[115,72320,3480],{"class":132},[115,72322,1984],{"class":132},[115,72324,1987],{"class":132},[115,72326,72327,72329,72331,72333,72335,72337,72339],{"class":117,"line":175},[115,72328,2001],{"class":262},[115,72330,5030],{"class":132},[115,72332,2788],{"class":202},[115,72334,2791],{"class":132},[115,72336,2794],{"class":202},[115,72338,2797],{"class":202},[115,72340,2800],{"class":202},[115,72342,72343,72345],{"class":117,"line":350},[115,72344,52246],{"class":262},[115,72346,52249],{"class":202},[16,72348,72349],{},"If reload does not recover cleanly, do a controlled restart during a safe window:",[106,72351,72352],{"className":108,"code":3471,"language":110,"meta":111,"style":111},[20,72353,72354],{"__ignoreMap":111},[115,72355,72356,72358,72360,72362],{"class":117,"line":118},[115,72357,2001],{"class":262},[115,72359,3480],{"class":132},[115,72361,3483],{"class":132},[115,72363,1987],{"class":132},[16,72365,72366],{},"Keep one tested last-known-good config file available for emergency restore.",[11,72368,1321],{"id":1320},[16,72370,72371],{},"This approach works because it treats Gunicorn tuning as a capacity and safety problem, not just a single number to maximize.",[16,72373,72374,72376,72377,72379,72380,72382,72383,72385],{},[20,72375,52856],{}," workers are usually the correct default because they are operationally simple and map cleanly to common Django deployments. ",[20,72378,52862],{}," can help when requests spend meaningful time waiting on I\u002FO, but it should be introduced carefully and measured. ",[20,72381,71214],{}," helps with long-running process growth, while conservative ",[20,72384,47977],{}," values prevent truly stuck workers from hanging forever.",[16,72387,72388],{},"The main reason Gunicorn performance tuning for Django goes wrong is that teams increase workers before checking memory, CPU, or database saturation. That can shift the bottleneck from request queueing to swapping, connection exhaustion, or noisy reload behavior.",[52,72390,72392],{"id":72391},"when-manual-tuning-becomes-repetitive","When manual tuning becomes repetitive",[16,72394,72395,72396,72398],{},"If you manage several environments, manual edits to Gunicorn settings become error-prone. At that point, it makes sense to standardize a ",[20,72397,71594],{}," template, keep environment-specific values in variables, and script a repeatable validation sequence after each reload. Good first automation targets are config backup, safe reload, metrics capture, and post-change health checks.",[11,72400,1337],{"id":1336},[63,72402,72403,72408,72414,72417,72420,72423],{},[66,72404,72405,72406,211],{},"If your app has long-running requests, investigate query performance, background jobs, or upstream API latency before increasing ",[20,72407,47977],{},[66,72409,6168,72410,72413],{},[20,72411,72412],{},"preload_app = True",", test DB connections, startup hooks, and memory behavior carefully before using it in production.",[66,72415,72416],{},"If you use containers, the same tuning rules apply, but CPU and memory limits from the orchestrator must be included in sizing.",[66,72418,72419],{},"If Nginx or Caddy has shorter upstream timeouts than Gunicorn, users may still see failures even when workers are alive.",[66,72421,72422],{},"Each additional Gunicorn process can indirectly increase PostgreSQL pressure if request concurrency rises.",[66,72424,72425],{},"If your workload is mostly async or connection-heavy, reconsider the stack instead of only increasing Gunicorn worker counts.",[11,72427,1386],{"id":1385},[16,72429,72430],{},"For related deployment topics, see:",[63,72432,72433,72438,72442,72447],{},[66,72434,72435],{},[1395,72436,72437],{"href":2985},"How Gunicorn works with Django in production",[66,72439,72440],{},[1395,72441,2986],{"href":2985},[66,72443,72444],{},[1395,72445,72446],{"href":32218},"Configure systemd for Gunicorn Django",[66,72448,72449],{},[1395,72450,72451],{"href":6332},"How to fix Gunicorn worker timeouts in Django",[11,72453,1420],{"id":1419},[52,72455,72457],{"id":72456},"how-many-gunicorn-workers-should-a-django-app-use","How many Gunicorn workers should a Django app use?",[16,72459,37788,72460,72463],{},[20,72461,72462],{},"2 * CPU + 1"," for sync workers, then test. On small servers, fewer workers are often better if memory is tight or the app is CPU-heavy.",[52,72465,1434,72467,4493,72469,72471],{"id":72466},"should-i-use-sync-or-gthread-workers-for-django",[20,72468,52856],{},[20,72470,52862],{}," workers for Django?",[16,72473,55,72474,72476,72477,72479],{},[20,72475,52856],{}," for most standard Django apps. Use ",[20,72478,52862],{}," only when the workload is moderately I\u002FO-bound and you have measured that threads improve latency or throughput.",[52,72481,72483],{"id":72482},"why-did-increasing-gunicorn-workers-make-performance-worse","Why did increasing Gunicorn workers make performance worse?",[16,72485,72486],{},"Common reasons are RAM pressure, CPU oversubscription, higher context switching, more database contention, or connection limits being reached.",[52,72488,72490],{"id":72489},"do-more-gunicorn-workers-require-more-postgresql-connections","Do more Gunicorn workers require more PostgreSQL connections?",[16,72492,72493],{},"Potentially yes. More app concurrency can lead to more simultaneous database activity, so PostgreSQL limits and pooling strategy should be considered during tuning.",[1485,72495,72496],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":111,"searchDepth":149,"depth":149,"links":72498},[72499,72500,72501,72521,72524,72525,72526],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":72502},[72503,72504,72505,72506,72515,72516,72517,72518,72519,72520],{"id":71264,"depth":149,"text":71265},{"id":71314,"depth":149,"text":71315},{"id":71412,"depth":149,"text":71413},{"id":71505,"depth":149,"text":71506,"children":72507},[72508,72510,72512,72514],{"id":71509,"depth":162,"text":72509},"sync workers for typical Django apps",{"id":71521,"depth":162,"text":72511},"gthread workers for moderate I\u002FO-bound workloads",{"id":71568,"depth":162,"text":72513},"gevent and eventlet caveats",{"id":71581,"depth":162,"text":71582},{"id":71588,"depth":149,"text":71589},{"id":71816,"depth":149,"text":71817},{"id":72064,"depth":149,"text":72065},{"id":72149,"depth":149,"text":72150},{"id":72187,"depth":149,"text":72188},{"id":19932,"depth":149,"text":72254},{"id":1320,"depth":136,"text":1321,"children":72522},[72523],{"id":72391,"depth":149,"text":72392},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":72527},[72528,72529,72531,72532],{"id":72456,"depth":149,"text":72457},{"id":72466,"depth":149,"text":72530},"Should I use sync or gthread workers for Django?",{"id":72482,"depth":149,"text":72483},{"id":72489,"depth":149,"text":72490},{},"\u002Foptimize-gunicorn-workers-django",[37876,1552,35631],{"title":71143,"description":71150},[1557,14954],"optimize-gunicorn-workers-django",[1557,14954],"0N6vP4rJFZDnMocss1dpCaT-IayduqzyOhdNbTDelv0",{"id":72542,"title":72543,"body":72544,"category":1541,"description":73721,"difficulty":23035,"extension":1544,"funnel_stage":23036,"intent":1546,"meta":73722,"navigation":309,"path":73723,"priority":61631,"related":73724,"role":23035,"section":1554,"seo":73726,"stack":73727,"stem":73728,"tags":73729,"type":1561,"__hash__":73730},"articles\u002Frollback-django-release-safely.md","How to Roll Back a Django Release Safely",{"type":8,"value":72545,"toc":73682},[72546,72548,72551,72554,72557,72568,72570,72573,72599,72602,72604,72608,72612,72615,72656,72659,72708,72710,72749,72753,72756,72770,72773,72787,72790,72794,72797,72799,72812,72815,72819,72822,72847,72850,72868,72871,72874,72877,72910,72914,72918,72921,72954,72957,72970,72973,72977,72980,73029,73039,73041,73058,73061,73065,73068,73110,73113,73117,73120,73152,73155,73174,73176,73197,73201,73204,73233,73238,73241,73255,73258,73276,73279,73290,73293,73297,73300,73303,73324,73330,73333,73337,73340,73377,73380,73399,73402,73435,73438,73457,73459,73462,73465,73479,73482,73486,73512,73516,73519,73560,73562,73565,73567,73571,73574,73584,73587,73597,73601,73604,73607,73611,73614,73616,73619,73621,73642,73644,73648,73651,73655,73658,73662,73665,73669,73672,73676,73679],[11,72547,14],{"id":13},[16,72549,72550],{},"A failed Django release can break more than page requests. It can also break background workers, scheduled jobs, static asset loading, startup commands, or database compatibility. In production, a safe Django rollback means restoring service without making data integrity, schema state, or security problems worse.",[16,72552,72553],{},"The main risk is assuming that rollback only means “point the server at older code.” That works for some failures, but not all. If the bad release changed environment variables, applied migrations, rotated assets, or wrote incompatible queue payloads, code rollback alone may leave the app in a half-broken state.",[16,72555,72556],{},"A safe rollback process needs to answer three questions first:",[1173,72558,72559,72562,72565],{},[66,72560,72561],{},"What actually failed?",[66,72563,72564],{},"Can the previous release run safely against the current database and environment?",[66,72566,72567],{},"Is rollback safer than a forward fix?",[11,72569,30],{"id":29},[16,72571,72572],{},"For a safe Django deployment rollback, use this order:",[1173,72574,72575,72578,72581,72584,72587,72590,72593,72596],{},[66,72576,72577],{},"Stop the rollout and freeze further changes.",[66,72579,72580],{},"Identify whether the failure is code, config, migration, static assets, queues, or infrastructure.",[66,72582,72583],{},"Put the system in a controlled state by pausing workers and scheduled jobs if needed.",[66,72585,72586],{},"Switch traffic back to the last known good application release.",[66,72588,72589],{},"Restart only the services that need the old release.",[66,72591,72592],{},"Reverse database migrations only if you have confirmed the downgrade is safe and tested.",[66,72594,72595],{},"Verify web requests, admin access, async workers, logs, static assets, and key business flows.",[66,72597,72598],{},"Document the incident and stabilize before attempting another deploy.",[16,72600,72601],{},"If migrations were destructive or the bad release already changed production data in an incompatible way, a forward fix or backup restore may be safer than rolling back code.",[11,72603,43],{"id":42},[11,72605,72607],{"id":72606},"identify-what-failed-before-you-roll-back","Identify what failed before you roll back",[52,72609,72611],{"id":72610},"determine-the-failure-type","Determine the failure type",[16,72613,72614],{},"Classify the incident before touching production:",[63,72616,72617,72623,72632,72638,72644,72650],{},[66,72618,72619,72622],{},[1226,72620,72621],{},"Bad application code",": exceptions after startup, broken view logic, failed imports",[66,72624,72625,72628,72629,72631],{},[1226,72626,72627],{},"Bad environment configuration",": missing env vars, wrong ",[20,72630,2719],{},", bad secrets, incorrect database or Redis settings",[66,72633,72634,72637],{},[1226,72635,72636],{},"Broken migration",": migration applied partially, app expects schema that no longer matches",[66,72639,72640,72643],{},[1226,72641,72642],{},"Missing static assets",": CSS\u002FJS 404s, stale asset manifest, release points to wrong static directory",[66,72645,72646,72649],{},[1226,72647,72648],{},"Process startup failure",": Gunicorn or Uvicorn fails to boot, worker crash loop",[66,72651,72652,72655],{},[1226,72653,72654],{},"Dependency or image issue",": broken Docker image, bad Python package build, missing system libraries",[16,72657,72658],{},"Check logs before rolling back:",[106,72660,72662],{"className":108,"code":72661,"language":110,"meta":111,"style":111},"systemctl status gunicorn\njournalctl -u gunicorn -n 100 --no-pager\nsystemctl status celery\njournalctl -u celery -n 100 --no-pager\n",[20,72663,72664,72672,72686,72694],{"__ignoreMap":111},[115,72665,72666,72668,72670],{"class":117,"line":118},[115,72667,1981],{"class":262},[115,72669,1984],{"class":132},[115,72671,1987],{"class":132},[115,72673,72674,72676,72678,72680,72682,72684],{"class":117,"line":136},[115,72675,2785],{"class":262},[115,72677,2788],{"class":202},[115,72679,2791],{"class":132},[115,72681,2794],{"class":202},[115,72683,2797],{"class":202},[115,72685,2800],{"class":202},[115,72687,72688,72690,72692],{"class":117,"line":149},[115,72689,1981],{"class":262},[115,72691,1984],{"class":132},[115,72693,4703],{"class":132},[115,72695,72696,72698,72700,72702,72704,72706],{"class":117,"line":162},[115,72697,2785],{"class":262},[115,72699,2788],{"class":202},[115,72701,5005],{"class":132},[115,72703,2794],{"class":202},[115,72705,2797],{"class":202},[115,72707,2800],{"class":202},[16,72709,31253],{},[106,72711,72713],{"className":108,"code":72712,"language":110,"meta":111,"style":111},"docker compose ps\ndocker compose logs --tail=100 web\ndocker compose logs --tail=100 worker\n",[20,72714,72715,72723,72736],{"__ignoreMap":111},[115,72716,72717,72719,72721],{"class":117,"line":118},[115,72718,3295],{"class":262},[115,72720,3298],{"class":132},[115,72722,4790],{"class":132},[115,72724,72725,72727,72729,72731,72734],{"class":117,"line":136},[115,72726,3295],{"class":262},[115,72728,3298],{"class":132},[115,72730,3301],{"class":132},[115,72732,72733],{"class":202}," --tail=100",[115,72735,3510],{"class":132},[115,72737,72738,72740,72742,72744,72746],{"class":117,"line":149},[115,72739,3295],{"class":262},[115,72741,3298],{"class":132},[115,72743,3301],{"class":132},[115,72745,72733],{"class":202},[115,72747,72748],{"class":132}," worker\n",[52,72750,72752],{"id":72751},"decide-whether-rollback-is-safe","Decide whether rollback is safe",[16,72754,72755],{},"Code-only rollback is usually safe when:",[63,72757,72758,72761,72764,72767],{},[66,72759,72760],{},"no incompatible migration was applied",[66,72762,72763],{},"environment variables still match the previous release",[66,72765,72766],{},"queue payloads and scheduled jobs are still compatible",[66,72768,72769],{},"static assets for the old release are still available",[16,72771,72772],{},"Rollback is risky when:",[63,72774,72775,72778,72781,72784],{},[66,72776,72777],{},"columns were dropped or renamed",[66,72779,72780],{},"tables were renamed and old code expects the old schema",[66,72782,72783],{},"one-way data migrations already transformed production data",[66,72785,72786],{},"the failed release changed secrets or security-sensitive settings",[16,72788,72789],{},"In those cases, reverting to the previous release may mean a forward fix or backup restore, not just switching code.",[52,72791,72793],{"id":72792},"freeze-the-release","Freeze the release",[16,72795,72796],{},"Stop automation first so the bad rollout does not continue.",[16,72798,38745],{},[106,72800,72802],{"className":108,"code":72801,"language":110,"meta":111,"style":111},"touch \u002Fsrv\u002Fmyapp\u002Fshared\u002FDEPLOY_LOCK\n",[20,72803,72804],{"__ignoreMap":111},[115,72805,72806,72809],{"class":117,"line":118},[115,72807,72808],{"class":262},"touch",[115,72810,72811],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fshared\u002FDEPLOY_LOCK\n",[16,72813,72814],{},"Pause your CI\u002FCD pipeline in its own UI, and disable any deployment webhook if you use one. Also make sure one operator owns the rollback so multiple people are not changing the same system at once.",[52,72816,72818],{"id":72817},"step-1-put-the-system-in-a-controlled-state","Step 1: Put the system in a controlled state",[16,72820,72821],{},"If background jobs can write incompatible data, stop them before switching releases.",[106,72823,72825],{"className":108,"code":72824,"language":110,"meta":111,"style":111},"systemctl stop celery\nsystemctl stop celerybeat  # or your actual beat unit name, such as celery-beat\n",[20,72826,72827,72835],{"__ignoreMap":111},[115,72828,72829,72831,72833],{"class":117,"line":118},[115,72830,1981],{"class":262},[115,72832,9987],{"class":132},[115,72834,4703],{"class":132},[115,72836,72837,72839,72841,72844],{"class":117,"line":136},[115,72838,1981],{"class":262},[115,72840,9987],{"class":132},[115,72842,72843],{"class":132}," celerybeat",[115,72845,72846],{"class":3861},"  # or your actual beat unit name, such as celery-beat\n",[16,72848,72849],{},"If using Compose:",[106,72851,72853],{"className":108,"code":72852,"language":110,"meta":111,"style":111},"docker compose stop worker beat\n",[20,72854,72855],{"__ignoreMap":111},[115,72856,72857,72859,72861,72863,72865],{"class":117,"line":118},[115,72858,3295],{"class":262},[115,72860,3298],{"class":132},[115,72862,9987],{"class":132},[115,72864,4801],{"class":132},[115,72866,72867],{"class":132}," beat\n",[16,72869,72870],{},"If the failed release published incompatible task payloads, do not restart old workers blindly. Inspect queued jobs first and decide whether they can be drained, discarded, rerouted, or handled by a forward fix.",[16,72872,72873],{},"If needed, enable maintenance mode for all traffic or only for admin users. Keep the health endpoint available so you can still verify recovery through the proxy.",[16,72875,72876],{},"Example Nginx health passthrough:",[106,72878,72880],{"className":2154,"code":72879,"language":2156,"meta":111,"style":111},"location \u002Fhealth\u002F {\n    access_log off;\n    proxy_pass http:\u002F\u002Fapp;\n}\n",[20,72881,72882,72891,72899,72906],{"__ignoreMap":111},[115,72883,72884,72886,72889],{"class":117,"line":118},[115,72885,7128],{"class":121},[115,72887,72888],{"class":262}," \u002Fhealth\u002F ",[115,72890,2220],{"class":125},[115,72892,72893,72895,72897],{"class":117,"line":136},[115,72894,55690],{"class":121},[115,72896,7103],{"class":202},[115,72898,3811],{"class":125},[115,72900,72901,72903],{"class":117,"line":149},[115,72902,7137],{"class":121},[115,72904,72905],{"class":125},"http:\u002F\u002Fapp;\n",[115,72907,72908],{"class":117,"line":162},[115,72909,2323],{"class":125},[52,72911,72913],{"id":72912},"step-2-switch-to-the-previous-application-release","Step 2: Switch to the previous application release",[1850,72915,72917],{"id":72916},"symlink-based-rollback","Symlink-based rollback",[16,72919,72920],{},"For versioned Linux releases:",[106,72922,72924],{"className":108,"code":72923,"language":110,"meta":111,"style":111},"ls -1 \u002Fsrv\u002Fmyapp\u002Freleases\nreadlink -f \u002Fsrv\u002Fmyapp\u002Fcurrent\nln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F2026-04-20-120000 \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[20,72925,72926,72935,72943],{"__ignoreMap":111},[115,72927,72928,72930,72933],{"class":117,"line":118},[115,72929,532],{"class":262},[115,72931,72932],{"class":202}," -1",[115,72934,14836],{"class":132},[115,72936,72937,72939,72941],{"class":117,"line":136},[115,72938,31686],{"class":262},[115,72940,2777],{"class":202},[115,72942,5306],{"class":132},[115,72944,72945,72947,72949,72952],{"class":117,"line":149},[115,72946,14854],{"class":262},[115,72948,14857],{"class":202},[115,72950,72951],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F2026-04-20-120000",[115,72953,5306],{"class":132},[16,72955,72956],{},"Verify the symlink:",[106,72958,72960],{"className":108,"code":72959,"language":110,"meta":111,"style":111},"readlink -f \u002Fsrv\u002Fmyapp\u002Fcurrent\n",[20,72961,72962],{"__ignoreMap":111},[115,72963,72964,72966,72968],{"class":117,"line":118},[115,72965,31686],{"class":262},[115,72967,2777],{"class":202},[115,72969,5306],{"class":132},[16,72971,72972],{},"This is the cleanest safe Django rollback pattern because release artifacts stay immutable and you can move between known versions quickly.",[1850,72974,72976],{"id":72975},"docker-or-compose-rollback","Docker or Compose rollback",[16,72978,72979],{},"Roll back to a previously built image tag. Do not rebuild during an incident unless you are intentionally doing a forward fix.",[106,72981,72983],{"className":108,"code":72982,"language":110,"meta":111,"style":111},"docker compose ps\ndocker image ls\nexport IMAGE_TAG=2026-04-20-120000\ndocker compose up -d web worker\n",[20,72984,72985,72993,73003,73015],{"__ignoreMap":111},[115,72986,72987,72989,72991],{"class":117,"line":118},[115,72988,3295],{"class":262},[115,72990,3298],{"class":132},[115,72992,4790],{"class":132},[115,72994,72995,72997,73000],{"class":117,"line":136},[115,72996,3295],{"class":262},[115,72998,72999],{"class":132}," image",[115,73001,73002],{"class":132}," ls\n",[115,73004,73005,73007,73010,73012],{"class":117,"line":149},[115,73006,122],{"class":121},[115,73008,73009],{"class":125}," IMAGE_TAG",[115,73011,129],{"class":121},[115,73013,73014],{"class":125},"2026-04-20-120000\n",[115,73016,73017,73019,73021,73023,73025,73027],{"class":117,"line":162},[115,73018,3295],{"class":262},[115,73020,3298],{"class":132},[115,73022,3502],{"class":132},[115,73024,1019],{"class":202},[115,73026,3304],{"class":132},[115,73028,72748],{"class":132},[16,73030,73031,73032,23468,73035,73038],{},"This assumes your ",[20,73033,73034],{},"compose.yaml",[20,73036,73037],{},"${IMAGE_TAG}"," in the image reference; if not, update the image tag in the Compose configuration or deploy using your normal image-pinning method.",[16,73040,43213],{},[106,73042,73044],{"className":108,"code":73043,"language":110,"meta":111,"style":111},"docker compose logs --tail=100 web\n",[20,73045,73046],{"__ignoreMap":111},[115,73047,73048,73050,73052,73054,73056],{"class":117,"line":118},[115,73049,3295],{"class":262},[115,73051,3298],{"class":132},[115,73053,3301],{"class":132},[115,73055,72733],{"class":202},[115,73057,3510],{"class":132},[16,73059,73060],{},"Make sure the previous image tag still matches current mounted volumes and environment variables.",[1850,73062,73064],{"id":73063},"git-based-rollback","Git-based rollback",[16,73066,73067],{},"For simpler setups:",[106,73069,73071],{"className":108,"code":73070,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fapp\ngit log --oneline -n 5\ngit checkout \u003Cprevious-good-commit>\n",[20,73072,73073,73080,73095],{"__ignoreMap":111},[115,73074,73075,73077],{"class":117,"line":118},[115,73076,5303],{"class":202},[115,73078,73079],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fapp\n",[115,73081,73082,73084,73087,73090,73092],{"class":117,"line":136},[115,73083,13525],{"class":262},[115,73085,73086],{"class":132}," log",[115,73088,73089],{"class":202}," --oneline",[115,73091,2794],{"class":202},[115,73093,73094],{"class":202}," 5\n",[115,73096,73097,73099,73101,73103,73106,73108],{"class":117,"line":149},[115,73098,13525],{"class":262},[115,73100,30452],{"class":132},[115,73102,7691],{"class":121},[115,73104,73105],{"class":132},"previous-good-commi",[115,73107,30460],{"class":125},[115,73109,17380],{"class":121},[16,73111,73112],{},"This is workable, but less reliable than immutable release directories or image tags because dependencies and build artifacts may drift. If you use this method, also verify that the virtualenv, Python dependencies, and built assets still match that commit before restarting services.",[52,73114,73116],{"id":73115},"step-3-restart-only-the-required-services","Step 3: Restart only the required services",[16,73118,73119],{},"Restart the app server and related services tied to the release.",[106,73121,73123],{"className":108,"code":73122,"language":110,"meta":111,"style":111},"systemctl restart gunicorn\nsystemctl restart celery\nsystemctl restart celerybeat  # or your actual beat unit name\n",[20,73124,73125,73133,73141],{"__ignoreMap":111},[115,73126,73127,73129,73131],{"class":117,"line":118},[115,73128,1981],{"class":262},[115,73130,3483],{"class":132},[115,73132,1987],{"class":132},[115,73134,73135,73137,73139],{"class":117,"line":136},[115,73136,1981],{"class":262},[115,73138,3483],{"class":132},[115,73140,4703],{"class":132},[115,73142,73143,73145,73147,73149],{"class":117,"line":149},[115,73144,1981],{"class":262},[115,73146,3483],{"class":132},[115,73148,72843],{"class":132},[115,73150,73151],{"class":3861},"  # or your actual beat unit name\n",[16,73153,73154],{},"If you use Uvicorn under systemd, restart that service instead. Reload Nginx only if its configuration changed:",[106,73156,73158],{"className":108,"code":73157,"language":110,"meta":111,"style":111},"nginx -t && systemctl reload nginx\n",[20,73159,73160],{"__ignoreMap":111},[115,73161,73162,73164,73166,73168,73170,73172],{"class":117,"line":118},[115,73163,2156],{"class":262},[115,73165,3909],{"class":202},[115,73167,3912],{"class":125},[115,73169,1981],{"class":262},[115,73171,3919],{"class":132},[115,73173,1996],{"class":132},[16,73175,8572],{},[106,73177,73179],{"className":108,"code":73178,"language":110,"meta":111,"style":111},"systemctl status gunicorn\nsystemctl status celery\n",[20,73180,73181,73189],{"__ignoreMap":111},[115,73182,73183,73185,73187],{"class":117,"line":118},[115,73184,1981],{"class":262},[115,73186,1984],{"class":132},[115,73188,1987],{"class":132},[115,73190,73191,73193,73195],{"class":117,"line":136},[115,73192,1981],{"class":262},[115,73194,1984],{"class":132},[115,73196,4703],{"class":132},[52,73198,73200],{"id":73199},"step-4-handle-database-migrations-carefully","Step 4: Handle database migrations carefully",[16,73202,73203],{},"Check current migration state first:",[106,73205,73207],{"className":108,"code":73206,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\npython manage.py showmigrations\npython manage.py migrate --plan\n",[20,73208,73209,73215,73223],{"__ignoreMap":111},[115,73210,73211,73213],{"class":117,"line":118},[115,73212,5303],{"class":202},[115,73214,5306],{"class":132},[115,73216,73217,73219,73221],{"class":117,"line":136},[115,73218,1114],{"class":262},[115,73220,1117],{"class":132},[115,73222,1129],{"class":132},[115,73224,73225,73227,73229,73231],{"class":117,"line":149},[115,73226,1114],{"class":262},[115,73228,1117],{"class":132},[115,73230,1826],{"class":132},[115,73232,1829],{"class":202},[16,73234,7471,73235,73237],{},[1226,73236,7474],{}," reverse migrations automatically as part of every rollback.",[16,73239,73240],{},"A downgrade may be acceptable only when:",[63,73242,73243,73246,73249,73252],{},[66,73244,73245],{},"the migration is reversible",[66,73247,73248],{},"no destructive schema change occurred",[66,73250,73251],{},"old code is compatible with the downgraded schema",[66,73253,73254],{},"you have tested the downgrade path in staging",[16,73256,73257],{},"Example, only if confirmed safe:",[106,73259,73261],{"className":108,"code":73260,"language":110,"meta":111,"style":111},"python manage.py migrate app_name 0012_previous\n",[20,73262,73263],{"__ignoreMap":111},[115,73264,73265,73267,73269,73271,73273],{"class":117,"line":118},[115,73266,1114],{"class":262},[115,73268,1117],{"class":132},[115,73270,1826],{"class":132},[115,73272,38284],{"class":132},[115,73274,73275],{"class":132}," 0012_previous\n",[16,73277,73278],{},"Avoid rollback migrations in production when:",[63,73280,73281,73284,73287],{},[66,73282,73283],{},"columns were dropped",[66,73285,73286],{},"data was transformed in a one-way migration",[66,73288,73289],{},"new production writes depend on the new schema",[16,73291,73292],{},"In those cases, reversing migrations in production may cause more damage than the original release. If schema or data is corrupted, restoring from backup may be the only safe recovery path.",[52,73294,73296],{"id":73295},"step-5-restore-static-assets-consistency","Step 5: Restore static assets consistency",[16,73298,73299],{},"If each release has its own static build, make sure the old release points to matching assets. If static files are shared, verify the manifest is still valid for the previous code.",[16,73301,73302],{},"If needed:",[106,73304,73306],{"className":108,"code":73305,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\npython manage.py collectstatic --noinput\n",[20,73307,73308,73314],{"__ignoreMap":111},[115,73309,73310,73312],{"class":117,"line":118},[115,73311,5303],{"class":202},[115,73313,5306],{"class":132},[115,73315,73316,73318,73320,73322],{"class":117,"line":136},[115,73317,1114],{"class":262},[115,73319,1117],{"class":132},[115,73321,1838],{"class":132},[115,73323,1841],{"class":202},[16,73325,73326,73327,73329],{},"Use this carefully. Running ",[20,73328,13689],{}," during an incident is fine if your storage design supports it, but it changes state. If your deployment already versions static assets correctly, simply repointing the release is often enough.",[16,73331,73332],{},"Check asset responses in the browser and network logs, not just HTML status codes.",[52,73334,73336],{"id":73335},"step-6-verify-the-rollback","Step 6: Verify the rollback",[16,73338,73339],{},"Run application and infrastructure checks immediately.",[106,73341,73343],{"className":108,"code":73342,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fhealth\u002F\ncurl -I https:\u002F\u002Fexample.com\u002F\ncd \u002Fsrv\u002Fmyapp\u002Fcurrent\npython manage.py check --deploy\n",[20,73344,73345,73353,73361,73367],{"__ignoreMap":111},[115,73346,73347,73349,73351],{"class":117,"line":118},[115,73348,2764],{"class":262},[115,73350,2767],{"class":202},[115,73352,13426],{"class":132},[115,73354,73355,73357,73359],{"class":117,"line":136},[115,73356,2764],{"class":262},[115,73358,2767],{"class":202},[115,73360,32984],{"class":132},[115,73362,73363,73365],{"class":117,"line":149},[115,73364,5303],{"class":202},[115,73366,5306],{"class":132},[115,73368,73369,73371,73373,73375],{"class":117,"line":162},[115,73370,1114],{"class":262},[115,73372,1117],{"class":132},[115,73374,1814],{"class":132},[115,73376,1817],{"class":202},[16,73378,73379],{},"Then verify:",[63,73381,73382,73384,73387,73390,73393,73396],{},[66,73383,17744],{},[66,73385,73386],{},"one key read and write flow works",[66,73388,73389],{},"worker queue starts processing normally",[66,73391,73392],{},"scheduled jobs are restored only after web compatibility is confirmed",[66,73394,73395],{},"static assets load correctly",[66,73397,73398],{},"error rate and latency return to baseline",[16,73400,73401],{},"For logs:",[106,73403,73405],{"className":108,"code":73404,"language":110,"meta":111,"style":111},"journalctl -u gunicorn -n 100 --no-pager\njournalctl -u celery -n 100 --no-pager\n",[20,73406,73407,73421],{"__ignoreMap":111},[115,73408,73409,73411,73413,73415,73417,73419],{"class":117,"line":118},[115,73410,2785],{"class":262},[115,73412,2788],{"class":202},[115,73414,2791],{"class":132},[115,73416,2794],{"class":202},[115,73418,2797],{"class":202},[115,73420,2800],{"class":202},[115,73422,73423,73425,73427,73429,73431,73433],{"class":117,"line":136},[115,73424,2785],{"class":262},[115,73426,2788],{"class":202},[115,73428,5005],{"class":132},[115,73430,2794],{"class":202},[115,73432,2797],{"class":202},[115,73434,2800],{"class":202},[16,73436,73437],{},"For Compose:",[106,73439,73441],{"className":108,"code":73440,"language":110,"meta":111,"style":111},"docker compose logs --tail=100 web worker\n",[20,73442,73443],{"__ignoreMap":111},[115,73444,73445,73447,73449,73451,73453,73455],{"class":117,"line":118},[115,73446,3295],{"class":262},[115,73448,3298],{"class":132},[115,73450,3301],{"class":132},[115,73452,72733],{"class":202},[115,73454,3304],{"class":132},[115,73456,72748],{"class":132},[11,73458,1321],{"id":1320},[16,73460,73461],{},"A good Django release rollback process works because it separates code recovery from schema recovery. Most failed deploys are fixed by returning web and worker processes to the last known good release, but production incidents become dangerous when teams also reverse migrations, rotate services, and clear caches without deciding whether those changes are actually safe.",[16,73463,73464],{},"The safest default is:",[63,73466,73467,73470,73473,73476],{},[66,73468,73469],{},"freeze changes",[66,73471,73472],{},"roll back code",[66,73474,73475],{},"keep the database as-is unless a tested downgrade is clearly safe",[66,73477,73478],{},"verify web and async paths separately",[16,73480,73481],{},"This also explains why immutable releases matter. Symlinked release directories, tagged Docker images, and stored build artifacts make deployment recovery predictable. Git checkout on a live server is harder to reason about because code, virtualenv contents, and generated assets may not match.",[52,73483,73485],{"id":73484},"rollback-patterns-by-deployment-style","Rollback patterns by deployment style",[63,73487,73488,73497,73503],{},[66,73489,73490,73493,73494,73496],{},[1226,73491,73492],{},"Symlinked releases on Linux",": fastest manual rollback; ",[20,73495,13654],{}," points to a previous directory while shared media and env files remain stable",[66,73498,73499,73502],{},[1226,73500,73501],{},"Docker or Compose",": switch to the prior image tag and restart services without rebuilding",[66,73504,73505,73508,73509,73511],{},[1226,73506,73507],{},"systemd non-container deploys",": revert the active code or virtualenv path and inspect ",[20,73510,35702],{}," after restart",[52,73513,73515],{"id":73514},"security-and-operational-checks-during-rollback","Security and operational checks during rollback",[16,73517,73518],{},"During a rollback, check for drift:",[63,73520,73521,73524,73530,73539,73545,73548,73551,73554,73557],{},[66,73522,73523],{},"confirm the previous release still supports current environment variables",[66,73525,73526,73527,73529],{},"verify ",[20,73528,2713],{}," expectations, because a mismatch can invalidate sessions or break signed data",[66,73531,73532,73533,1153,73535,20346,73537],{},"confirm host and request settings such as ",[20,73534,2719],{},[20,73536,2725],{},[20,73538,7350],{},[66,73540,73541,73542,73544],{},"confirm proxy and TLS-related settings still match production, including ",[20,73543,2377],{}," where applicable",[66,73546,73547],{},"revoke secrets if the failed release exposed credentials or unsafe config",[66,73549,73550],{},"make sure old workers can handle current queue payloads before consuming them",[66,73552,73553],{},"disable feature flags tied to the failed release",[66,73555,73556],{},"clear or invalidate caches if cached structures changed",[66,73558,73559],{},"confirm session compatibility if serializers or auth settings changed",[52,73561,41766],{"id":41765},[16,73563,73564],{},"Manual rollback becomes repetitive once you have multiple services, release directories, and health checks. That is usually the point to standardize deploy locks, previous-release detection, ordered restarts, queue checks, and post-rollback smoke tests in a reusable script or runbook template. The manual sequence should stay the reference path even if you automate it later.",[11,73566,1337],{"id":1336},[52,73568,73570],{"id":73569},"reversible-vs-destructive-migrations","Reversible vs destructive migrations",[16,73572,73573],{},"Safe examples:",[63,73575,73576,73578,73581],{},[66,73577,39293],{},[66,73579,73580],{},"adding an index",[66,73582,73583],{},"adding a table not yet required by old code",[16,73585,73586],{},"Dangerous examples:",[63,73588,73589,73591,73594],{},[66,73590,39332],{},[66,73592,73593],{},"renaming tables used by old code",[66,73595,73596],{},"changing data formats with one-way migrations",[52,73598,73600],{"id":73599},"background-jobs","Background jobs",[16,73602,73603],{},"A common rollback failure is restoring web code but leaving Celery workers on the bad release, or letting old workers consume messages created by new code. Pause workers first, then restart them on the same release as the web app.",[16,73605,73606],{},"If the bad release enqueued incompatible jobs, rolling back web code is not enough. Inspect the queue and decide whether jobs should be drained, purged, rerouted, or replayed after a forward fix.",[52,73608,73610],{"id":73609},"sessions-cache-and-feature-flags","Sessions, cache, and feature flags",[16,73612,73613],{},"If the failed deploy changed cache keys, serializers, session signing expectations, or feature-flag behavior, stale state can make rollback appear unsuccessful. Clear only the affected cache layer if needed, disable release-specific flags before reopening traffic, and verify that login and CSRF-protected forms still work.",[52,73615,17443],{"id":17442},[16,73617,73618],{},"If the application wrote incompatible production data or an irreversible migration already ran, rollback may require database restore plus media reconciliation. That is a recovery event, not a normal deploy step, and should follow your backup validation runbook.",[11,73620,1386],{"id":1385},[16,73622,73623,73624,73626,73627,1153,73629,20346,73631,73633,73634,73637,73638,211],{},"If you are building your rollback process, start with ",[1395,73625,3000],{"href":2999},". For deployment baselines, see ",[1395,73628,2986],{"href":2985},[1395,73630,8039],{"href":8038},[1395,73632,8046],{"href":8045},". For release safety around schema changes, read ",[1395,73635,73636],{"href":1409},"how to run Django migrations safely in production",". If the release is still failing after rollback, use ",[1395,73639,73641],{"href":73640},"\u002Ffix-issues","how to troubleshoot a failed Django deployment",[11,73643,1420],{"id":1419},[52,73645,73647],{"id":73646},"can-i-roll-back-a-django-deployment-without-rolling-back-the-database","Can I roll back a Django deployment without rolling back the database?",[16,73649,73650],{},"Yes. That is often the safest first move. Roll back the application release, then verify whether the old code can run against the current schema. Only reverse migrations if you have confirmed the downgrade path is safe.",[52,73652,73654],{"id":73653},"when-should-i-avoid-reversing-django-migrations-in-production","When should I avoid reversing Django migrations in production?",[16,73656,73657],{},"Avoid it when migrations are destructive, one-way, or already changed live data in a way the old code cannot safely use. In those cases, a forward fix or backup restore is usually safer.",[52,73659,73661],{"id":73660},"how-do-i-roll-back-celery-workers-after-a-bad-django-release","How do I roll back Celery workers after a bad Django release?",[16,73663,73664],{},"Stop workers and beat first, switch them to the same previous release or image tag as the web app, then restart them after web health is confirmed. Also check whether queued messages from the failed release are still compatible before letting old workers consume them.",[52,73666,73668],{"id":73667},"what-should-i-check-immediately-after-a-django-deployment-rollback","What should I check immediately after a Django deployment rollback?",[16,73670,73671],{},"Check the health endpoint, homepage, admin login, one critical database write path, worker processing, service status, recent logs, and static asset loading. Also confirm that error rates drop back to normal.",[52,73673,73675],{"id":73674},"when-is-a-forward-fix-safer-than-a-rollback","When is a forward fix safer than a rollback?",[16,73677,73678],{},"A forward fix is safer when the bad release applied irreversible schema changes, transformed production data, changed queue contracts, or changed runtime assumptions that older code cannot understand. In those cases, forcing an older release back into production can extend the outage or damage data further.",[1485,73680,73681],{},"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 .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 .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}",{"title":111,"searchDepth":149,"depth":149,"links":73683},[73684,73685,73686,73687,73702,73707,73713,73714],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":72606,"depth":136,"text":72607,"children":73688},[73689,73690,73691,73692,73693,73698,73699,73700,73701],{"id":72610,"depth":149,"text":72611},{"id":72751,"depth":149,"text":72752},{"id":72792,"depth":149,"text":72793},{"id":72817,"depth":149,"text":72818},{"id":72912,"depth":149,"text":72913,"children":73694},[73695,73696,73697],{"id":72916,"depth":162,"text":72917},{"id":72975,"depth":162,"text":72976},{"id":73063,"depth":162,"text":73064},{"id":73115,"depth":149,"text":73116},{"id":73199,"depth":149,"text":73200},{"id":73295,"depth":149,"text":73296},{"id":73335,"depth":149,"text":73336},{"id":1320,"depth":136,"text":1321,"children":73703},[73704,73705,73706],{"id":73484,"depth":149,"text":73485},{"id":73514,"depth":149,"text":73515},{"id":41765,"depth":149,"text":41766},{"id":1336,"depth":136,"text":1337,"children":73708},[73709,73710,73711,73712],{"id":73569,"depth":149,"text":73570},{"id":73599,"depth":149,"text":73600},{"id":73609,"depth":149,"text":73610},{"id":17442,"depth":149,"text":17443},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":73715},[73716,73717,73718,73719,73720],{"id":73646,"depth":149,"text":73647},{"id":73653,"depth":149,"text":73654},{"id":73660,"depth":149,"text":73661},{"id":73667,"depth":149,"text":73668},{"id":73674,"depth":149,"text":73675},"A failed Django release can break more than page requests. It can also break background workers, scheduled jobs, static asset loading, startup commands, or database compatibility.",{},"\u002Frollback-django-release-safely",[37876,71135,73725],"\u002Foptimize\u002Fload-test-django-before-deploy",{"title":72543,"description":73721},[1557,13525,1277],"rollback-django-release-safely",[1557,13525,1277],"IH7rbbwe_G8_gS6ejCD6XN1rrWrgwAxMP-Gg1P53VPY",{"id":73732,"title":73733,"body":73734,"category":1541,"description":75238,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":75239,"navigation":309,"path":75240,"priority":16241,"related":75241,"role":1553,"section":1554,"seo":75242,"stack":75243,"stem":75244,"tags":75245,"type":1561,"__hash__":75246},"articles\u002Fdjango-log-rotation-linux.md","How to Rotate Django and Gunicorn Logs on Linux",{"type":8,"value":73735,"toc":75214},[73736,73738,73745,73752,73769,73771,73781,73784,73811,73817,73819,73823,73826,73829,73840,73843,73857,73860,73866,73871,73887,73889,73902,73904,73908,73911,73925,73935,73938,73957,73960,73966,73968,74021,74027,74029,74037,74039,74043,74046,74052,74055,74093,74096,74112,74115,74129,74131,74139,74142,74144,74148,74153,74231,74237,74240,74299,74312,74322,74324,74328,74331,74338,74358,74363,74366,74372,74375,74400,74402,74410,74413,74415,74419,74422,74429,74690,74701,74704,74715,74718,74720,74728,74730,74734,74737,74753,74756,74771,74774,74805,74808,74825,74828,74849,74852,74927,74933,74935,74939,74942,74945,74948,75002,75004,75008,75014,75017,75038,75040,75043,75065,75068,75086,75088,75150,75152,75156,75158,75168,75172,75174,75178,75187,75191,75197,75201,75204,75208,75211],[11,73737,14],{"id":13},[16,73739,73740,73741,73744],{},"Django and Gunicorn log rotation becomes a production issue as soon as your app writes request logs, error logs, or application logs to disk for more than a few days. Gunicorn access logs can grow quickly on busy sites, and Django app logs often include stack traces, warnings, and background task output. If you do not rotate them, ",[20,73742,73743],{},"\u002Fvar\u002Flog"," or your app disk can fill up and cause request failures, database issues, or failed deploys.",[16,73746,73747,73748,73751],{},"Misconfigured rotation is also risky. Renaming a log file is not enough if Gunicorn keeps its file handle open and continues writing to the old rotated file. Bad ownership or ",[20,73749,73750],{},"create"," settings can leave Gunicorn or Django unable to write new logs after rotation. The safe production goal is to:",[63,73753,73754,73757,73760,73763,73766],{},[66,73755,73756],{},"rotate files before they fill disk",[66,73758,73759],{},"keep a useful retention window",[66,73761,73762],{},"preserve permissions",[66,73764,73765],{},"make Gunicorn reopen log files cleanly",[66,73767,73768],{},"verify the service continues writing after rotation",[11,73770,30],{"id":29},[16,73772,73773,73774,73776,73777,73780],{},"Use Linux ",[20,73775,36906],{}," for file-based Gunicorn and Django logs, with explicit ownership, compression, retention, and a ",[20,73778,73779],{},"postrotate"," action that tells Gunicorn to reopen its log files.",[16,73782,73783],{},"Before trusting the setup in production:",[1173,73785,73786,73789,73798,73803,73806],{},[66,73787,73788],{},"confirm which logs are actually file-based",[66,73790,73791,73792,73794,73795],{},"configure a ",[20,73793,36906],{}," rule in ",[20,73796,73797],{},"\u002Fetc\u002Flogrotate.d\u002F",[66,73799,44234,73800,73802],{},[20,73801,73750],{}," so new files have the right permissions",[66,73804,73805],{},"signal Gunicorn after rotation",[66,73807,43291,73808,73810],{},[20,73809,37830],{}," and then force a rotation in a safe window",[16,73812,73813,73814,73816],{},"If Gunicorn logs only to ",[20,73815,35702],{}," or stdout\u002Fstderr, file rotation may not be needed for Gunicorn itself.",[11,73818,43],{"id":42},[52,73820,73822],{"id":73821},"_1-identify-which-logs-need-rotation","1. Identify which logs need rotation",[16,73824,73825],{},"Start by listing the log files your deployment actually writes to disk.",[16,73827,73828],{},"Common candidates:",[63,73830,73831,73834,73837],{},[66,73832,73833],{},"Gunicorn access log",[66,73835,73836],{},"Gunicorn error log",[66,73838,73839],{},"Django application log written via Python logging",[16,73841,73842],{},"Inspect your log directory:",[106,73844,73846],{"className":108,"code":73845,"language":110,"meta":111,"style":111},"ls -lah \u002Fvar\u002Flog\u002Fyour-app\u002F\n",[20,73847,73848],{"__ignoreMap":111},[115,73849,73850,73852,73854],{"class":117,"line":118},[115,73851,532],{"class":262},[115,73853,12216],{"class":202},[115,73855,73856],{"class":132}," \u002Fvar\u002Flog\u002Fyour-app\u002F\n",[16,73858,73859],{},"Typical layout:",[106,73861,73864],{"className":73862,"code":73863,"language":247,"meta":111},[245],"\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log\n\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n\u002Fvar\u002Flog\u002Fyour-app\u002Fdjango.log\n",[20,73865,73863],{"__ignoreMap":111},[16,73867,7471,73868,73870],{},[1226,73869,7474],{}," automatically combine unrelated services into one rule unless ownership and paths are consistent. Keep these separate if needed:",[63,73872,73873,73876,73879,73884],{},[66,73874,73875],{},"Nginx logs",[66,73877,73878],{},"Celery worker logs",[66,73880,73881,73882],{},"system logs managed by ",[20,73883,35702],{},[66,73885,73886],{},"database logs",[16,73888,17389],{},[63,73890,73891,73894],{},[66,73892,73893],{},"Confirm every file in your rotation rule is actually written by the same app or service group.",[66,73895,73896,73897,73899,73900,211],{},"Confirm you are not trying to rotate ",[20,73898,35702],{},"-managed logs with ",[20,73901,36906],{},[23099,73903],{},[52,73905,73907],{"id":73906},"_2-confirm-how-gunicorn-is-writing-logs","2. Confirm how Gunicorn is writing logs",[16,73909,73910],{},"Check the Gunicorn systemd unit:",[106,73912,73914],{"className":108,"code":73913,"language":110,"meta":111,"style":111},"systemctl cat gunicorn.service\n",[20,73915,73916],{"__ignoreMap":111},[115,73917,73918,73920,73922],{"class":117,"line":118},[115,73919,1981],{"class":262},[115,73921,4973],{"class":132},[115,73923,73924],{"class":132}," gunicorn.service\n",[16,73926,73927,73928,73931,73932,211],{},"If your service uses a different unit name, inspect that instead, such as ",[20,73929,73930],{},"myapp-gunicorn.service"," or an instance unit like ",[20,73933,73934],{},"gunicorn@myapp.service",[16,73936,73937],{},"Also inspect the running process:",[106,73939,73941],{"className":108,"code":73940,"language":110,"meta":111,"style":111},"ps -ef | grep '[g]unicorn'\n",[20,73942,73943],{"__ignoreMap":111},[115,73944,73945,73947,73950,73952,73954],{"class":117,"line":118},[115,73946,4830],{"class":262},[115,73948,73949],{"class":202}," -ef",[115,73951,579],{"class":121},[115,73953,4838],{"class":262},[115,73955,73956],{"class":132}," '[g]unicorn'\n",[16,73958,73959],{},"Look for flags like:",[106,73961,73964],{"className":73962,"code":73963,"language":247,"meta":111},[245],"--access-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log\n--error-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[20,73965,73963],{"__ignoreMap":111},[16,73967,70545],{},[106,73969,73971],{"className":2026,"code":73970,"language":2028,"meta":111,"style":111},"[Service]\nUser=appuser\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fyour-app\u002Fcurrent\nExecStart=\u002Fsrv\u002Fyour-app\u002Fvenv\u002Fbin\u002Fgunicorn your_project.wsgi:application \\\n    --workers 3 \\\n    --bind 127.0.0.1:8000 \\\n    --access-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log \\\n    --error-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[20,73972,73973,73977,73983,73989,73996,74003,74007,74011,74016],{"__ignoreMap":111},[115,73974,73975],{"class":117,"line":118},[115,73976,2060],{"class":262},[115,73978,73979,73981],{"class":117,"line":136},[115,73980,2065],{"class":121},[115,73982,34947],{"class":125},[115,73984,73985,73987],{"class":117,"line":149},[115,73986,2073],{"class":121},[115,73988,2076],{"class":125},[115,73990,73991,73993],{"class":117,"line":162},[115,73992,2081],{"class":121},[115,73994,73995],{"class":125},"=\u002Fsrv\u002Fyour-app\u002Fcurrent\n",[115,73997,73998,74000],{"class":117,"line":175},[115,73999,2107],{"class":121},[115,74001,74002],{"class":125},"=\u002Fsrv\u002Fyour-app\u002Fvenv\u002Fbin\u002Fgunicorn your_project.wsgi:application \\\n",[115,74004,74005],{"class":117,"line":350},[115,74006,15417],{"class":125},[115,74008,74009],{"class":117,"line":365},[115,74010,23722],{"class":125},[115,74012,74013],{"class":117,"line":380},[115,74014,74015],{"class":125},"    --access-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log \\\n",[115,74017,74018],{"class":117,"line":487},[115,74019,74020],{"class":125},"    --error-logfile \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[16,74022,74023,74024,74026],{},"If Gunicorn logs only to stdout or stderr and systemd captures that into ",[20,74025,35702],{},", file rotation for Gunicorn may not be needed. In that case, keep this page scoped to your Django file logs only.",[16,74028,17389],{},[63,74030,74031,74034],{},[66,74032,74033],{},"Confirm the exact access and error log paths.",[66,74035,74036],{},"If no file paths exist, do not create a file rotation rule for Gunicorn.",[23099,74038],{},[52,74040,74042],{"id":74041},"_3-choose-a-safe-log-directory-and-permissions-model","3. Choose a safe log directory and permissions model",[16,74044,74045],{},"A common location is:",[106,74047,74050],{"className":74048,"code":74049,"language":247,"meta":111},[245],"\u002Fvar\u002Flog\u002Fyour-app\u002F\n",[20,74051,74049],{"__ignoreMap":111},[16,74053,74054],{},"Create it if needed:",[106,74056,74058],{"className":108,"code":74057,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fvar\u002Flog\u002Fyour-app\nsudo chown appuser:www-data \u002Fvar\u002Flog\u002Fyour-app\nsudo chmod 0750 \u002Fvar\u002Flog\u002Fyour-app\n",[20,74059,74060,74071,74082],{"__ignoreMap":111},[115,74061,74062,74064,74066,74068],{"class":117,"line":118},[115,74063,2001],{"class":262},[115,74065,6721],{"class":132},[115,74067,1001],{"class":202},[115,74069,74070],{"class":132}," \u002Fvar\u002Flog\u002Fyour-app\n",[115,74072,74073,74075,74077,74080],{"class":117,"line":136},[115,74074,2001],{"class":262},[115,74076,6733],{"class":132},[115,74078,74079],{"class":132}," appuser:www-data",[115,74081,74070],{"class":132},[115,74083,74084,74086,74088,74091],{"class":117,"line":149},[115,74085,2001],{"class":262},[115,74087,12480],{"class":132},[115,74089,74090],{"class":202}," 0750",[115,74092,74070],{"class":132},[16,74094,74095],{},"Avoid world-readable logs. Production logs may contain:",[63,74097,74098,74101,74104,74107,74109],{},[66,74099,74100],{},"session identifiers",[66,74102,74103],{},"email addresses",[66,74105,74106],{},"IPs",[66,74108,54675],{},[66,74110,74111],{},"tokens or request fragments",[16,74113,74114],{},"Use least privilege. A good default is:",[63,74116,74117,74123],{},[66,74118,74119,74120],{},"directory: ",[20,74121,74122],{},"0750",[66,74124,74125,74126],{},"files: ",[20,74127,74128],{},"0640",[16,74130,17389],{},[63,74132,74133,74136],{},[66,74134,74135],{},"The service user can write logs.",[66,74137,74138],{},"Unrelated local users cannot read log contents.",[16,74140,74141],{},"Rollback note: if changing ownership breaks writes, restore the previous owner and group before reloading or restarting services. On hardened hosts, also verify SELinux or AppArmor policy if permissions look correct but writes still fail.",[23099,74143],{},[52,74145,74147],{"id":74146},"_4-create-a-logrotate-policy-for-django-and-gunicorn","4. Create a logrotate policy for Django and Gunicorn",[16,74149,8628,74150,241],{},[20,74151,74152],{},"\u002Fetc\u002Flogrotate.d\u002Fyour-app",[106,74154,74156],{"className":8444,"code":74155,"language":8446,"meta":111,"style":111},"\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log\n\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n\u002Fvar\u002Flog\u002Fyour-app\u002Fdjango.log {\n    daily\n    rotate 14\n    compress\n    delaycompress\n    missingok\n    notifempty\n    dateext\n    create 0640 appuser www-data\n    sharedscripts\n    postrotate\n        \u002Fbin\u002Fsystemctl kill -s USR1 gunicorn.service >\u002Fdev\u002Fnull 2>&1 || true\n    endscript\n}\n",[20,74157,74158,74163,74168,74173,74177,74181,74185,74189,74193,74197,74202,74207,74212,74217,74222,74227],{"__ignoreMap":111},[115,74159,74160],{"class":117,"line":118},[115,74161,74162],{},"\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-access.log\n",[115,74164,74165],{"class":117,"line":136},[115,74166,74167],{},"\u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[115,74169,74170],{"class":117,"line":149},[115,74171,74172],{},"\u002Fvar\u002Flog\u002Fyour-app\u002Fdjango.log {\n",[115,74174,74175],{"class":117,"line":162},[115,74176,36922],{},[115,74178,74179],{"class":117,"line":175},[115,74180,36927],{},[115,74182,74183],{"class":117,"line":350},[115,74184,36932],{},[115,74186,74187],{"class":117,"line":365},[115,74188,36937],{},[115,74190,74191],{"class":117,"line":380},[115,74192,36942],{},[115,74194,74195],{"class":117,"line":487},[115,74196,36947],{},[115,74198,74199],{"class":117,"line":2095},[115,74200,74201],{},"    dateext\n",[115,74203,74204],{"class":117,"line":2104},[115,74205,74206],{},"    create 0640 appuser www-data\n",[115,74208,74209],{"class":117,"line":2113},[115,74210,74211],{},"    sharedscripts\n",[115,74213,74214],{"class":117,"line":2122},[115,74215,74216],{},"    postrotate\n",[115,74218,74219],{"class":117,"line":2131},[115,74220,74221],{},"        \u002Fbin\u002Fsystemctl kill -s USR1 gunicorn.service >\u002Fdev\u002Fnull 2>&1 || true\n",[115,74223,74224],{"class":117,"line":2136},[115,74225,74226],{},"    endscript\n",[115,74228,74229],{"class":117,"line":2142},[115,74230,2323],{},[16,74232,12628,74233,74236],{},[20,74234,74235],{},"gunicorn.service"," with your actual systemd unit name.",[16,74238,74239],{},"What the main directives do:",[63,74241,74242,74248,74254,74260,74266,74272,74278,74284,74290],{},[66,74243,74244,74247],{},[20,74245,74246],{},"daily",": rotate once per day",[66,74249,74250,74253],{},[20,74251,74252],{},"rotate 14",": keep 14 archived log sets",[66,74255,74256,74259],{},[20,74257,74258],{},"compress",": gzip older logs",[66,74261,74262,74265],{},[20,74263,74264],{},"delaycompress",": leave the most recently rotated file uncompressed until the next cycle",[66,74267,74268,74271],{},[20,74269,74270],{},"missingok",": do not fail if a file is absent",[66,74273,74274,74277],{},[20,74275,74276],{},"notifempty",": skip empty logs",[66,74279,74280,74283],{},[20,74281,74282],{},"dateext",": use date-based suffixes",[66,74285,74286,74289],{},[20,74287,74288],{},"create 0640 appuser www-data",": create a new active file with safe permissions",[66,74291,74292,74295,74296,74298],{},[20,74293,74294],{},"sharedscripts",": run ",[20,74297,73779],{}," once for the whole block, not once per file",[16,74300,74301,74302,74305,74306,74308,74309,74311],{},"Some distributions or permission layouts may also require a ",[20,74303,74304],{},"su"," directive, but do not assume the app user is always the correct choice for files under ",[20,74307,73743],{},". If you need ",[20,74310,74304],{},", set it deliberately for your host and test it during a forced rotation.",[16,74313,74314,74315,74318,74319,74321],{},"If log growth is bursty, you can prefer ",[20,74316,74317],{},"size"," or a combined time-and-size policy if your distro supports it consistently. For many apps, ",[20,74320,74246],{}," is the simpler baseline.",[23099,74323],{},[52,74325,74327],{"id":74326},"_5-make-gunicorn-reopen-log-files-after-rotation","5. Make Gunicorn reopen log files after rotation",[16,74329,74330],{},"Renaming a log file alone is not enough. A running process may keep writing to the old file descriptor even after the file has been moved.",[16,74332,74333,74334,74337],{},"For Gunicorn, a common approach is signaling it with ",[20,74335,74336],{},"USR1"," so it reopens log files:",[106,74339,74341],{"className":8444,"code":74340,"language":8446,"meta":111,"style":111},"postrotate\n    \u002Fbin\u002Fsystemctl kill -s USR1 gunicorn.service >\u002Fdev\u002Fnull 2>&1 || true\nendscript\n",[20,74342,74343,74348,74353],{"__ignoreMap":111},[115,74344,74345],{"class":117,"line":118},[115,74346,74347],{},"postrotate\n",[115,74349,74350],{"class":117,"line":136},[115,74351,74352],{},"    \u002Fbin\u002Fsystemctl kill -s USR1 gunicorn.service >\u002Fdev\u002Fnull 2>&1 || true\n",[115,74354,74355],{"class":117,"line":149},[115,74356,74357],{},"endscript\n",[16,74359,12628,74360,74362],{},[20,74361,74235],{}," with your actual unit name.",[16,74364,74365],{},"This is usually safer than a full restart because it avoids unnecessary worker interruption. A restart for log rotation alone is typically excessive and increases deployment risk.",[16,74367,47393,74368,74371],{},[20,74369,74370],{},"copytruncate"," unless you cannot signal the process to reopen logs. On busy services it can lose log lines during rotation.",[16,74373,74374],{},"After rotation, check for deleted-but-still-open files:",[106,74376,74378],{"className":108,"code":74377,"language":110,"meta":111,"style":111},"sudo lsof | grep gunicorn | grep deleted\n",[20,74379,74380],{"__ignoreMap":111},[115,74381,74382,74384,74387,74389,74391,74393,74395,74397],{"class":117,"line":118},[115,74383,2001],{"class":262},[115,74385,74386],{"class":132}," lsof",[115,74388,579],{"class":121},[115,74390,4838],{"class":262},[115,74392,2791],{"class":132},[115,74394,579],{"class":121},[115,74396,4838],{"class":262},[115,74398,74399],{"class":132}," deleted\n",[16,74401,17389],{},[63,74403,74404,74407],{},[66,74405,74406],{},"No Gunicorn process is writing to a deleted rotated file.",[66,74408,74409],{},"New log entries appear in the new active log file.",[16,74411,74412],{},"If you are not using systemd, signal the Gunicorn master process directly using the method appropriate for your supervisor or process manager.",[23099,74414],{},[52,74416,74418],{"id":74417},"_6-handle-django-application-logs-correctly","6. Handle Django application logs correctly",[16,74420,74421],{},"If Django writes to files through Python logging, make sure you do not rotate the same file in two different ways.",[16,74423,74424,74425,74428],{},"A simple Django logging example using ",[20,74426,74427],{},"WatchedFileHandler"," on Linux:",[106,74430,74432],{"className":2369,"code":74431,"language":1114,"meta":111,"style":111},"LOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"formatters\": {\n        \"standard\": {\n            \"format\": \"%(asctime)s %(levelname)s %(name)s %(message)s\",\n        },\n    },\n    \"handlers\": {\n        \"app_file\": {\n            \"class\": \"logging.handlers.WatchedFileHandler\",\n            \"filename\": \"\u002Fvar\u002Flog\u002Fyour-app\u002Fdjango.log\",\n            \"level\": \"INFO\",\n            \"formatter\": \"standard\",\n        },\n    },\n    \"root\": {\n        \"handlers\": [\"app_file\"],\n        \"level\": \"INFO\",\n    },\n    \"loggers\": {\n        \"django\": {\n            \"handlers\": [\"app_file\"],\n            \"level\": \"INFO\",\n            \"propagate\": False,\n        },\n        \"your_app\": {\n            \"handlers\": [\"app_file\"],\n            \"level\": \"INFO\",\n            \"propagate\": False,\n        },\n    },\n}\n",[20,74433,74434,74442,74452,74462,74468,74474,74494,74498,74502,74508,74515,74525,74536,74546,74556,74560,74564,74570,74581,74591,74595,74601,74607,74617,74627,74637,74641,74648,74658,74668,74678,74682,74686],{"__ignoreMap":111},[115,74435,74436,74438,74440],{"class":117,"line":118},[115,74437,3337],{"class":202},[115,74439,2380],{"class":121},[115,74441,2166],{"class":125},[115,74443,74444,74446,74448,74450],{"class":117,"line":136},[115,74445,3346],{"class":132},[115,74447,2513],{"class":125},[115,74449,3351],{"class":202},[115,74451,3354],{"class":125},[115,74453,74454,74456,74458,74460],{"class":117,"line":149},[115,74455,3359],{"class":132},[115,74457,2513],{"class":125},[115,74459,3364],{"class":202},[115,74461,3354],{"class":125},[115,74463,74464,74466],{"class":117,"line":162},[115,74465,35988],{"class":132},[115,74467,3374],{"class":125},[115,74469,74470,74472],{"class":117,"line":175},[115,74471,35995],{"class":132},[115,74473,3374],{"class":125},[115,74475,74476,74478,74480,74482,74484,74486,74488,74490,74492],{"class":117,"line":350},[115,74477,36002],{"class":132},[115,74479,2513],{"class":125},[115,74481,331],{"class":132},[115,74483,36009],{"class":202},[115,74485,36012],{"class":202},[115,74487,36015],{"class":202},[115,74489,36021],{"class":202},[115,74491,331],{"class":132},[115,74493,3354],{"class":125},[115,74495,74496],{"class":117,"line":365},[115,74497,3398],{"class":125},[115,74499,74500],{"class":117,"line":380},[115,74501,3403],{"class":125},[115,74503,74504,74506],{"class":117,"line":487},[115,74505,3371],{"class":132},[115,74507,3374],{"class":125},[115,74509,74510,74513],{"class":117,"line":2095},[115,74511,74512],{"class":132},"        \"app_file\"",[115,74514,3374],{"class":125},[115,74516,74517,74519,74521,74523],{"class":117,"line":2104},[115,74518,3386],{"class":132},[115,74520,2513],{"class":125},[115,74522,36087],{"class":132},[115,74524,3354],{"class":125},[115,74526,74527,74529,74531,74534],{"class":117,"line":2113},[115,74528,36094],{"class":132},[115,74530,2513],{"class":125},[115,74532,74533],{"class":132},"\"\u002Fvar\u002Flog\u002Fyour-app\u002Fdjango.log\"",[115,74535,3354],{"class":125},[115,74537,74538,74540,74542,74544],{"class":117,"line":2122},[115,74539,3435],{"class":132},[115,74541,2513],{"class":125},[115,74543,35898],{"class":132},[115,74545,3354],{"class":125},[115,74547,74548,74550,74552,74554],{"class":117,"line":2131},[115,74549,36060],{"class":132},[115,74551,2513],{"class":125},[115,74553,36065],{"class":132},[115,74555,3354],{"class":125},[115,74557,74558],{"class":117,"line":2136},[115,74559,3398],{"class":125},[115,74561,74562],{"class":117,"line":2142},[115,74563,3403],{"class":125},[115,74565,74566,74568],{"class":117,"line":2273},[115,74567,36132],{"class":132},[115,74569,3374],{"class":125},[115,74571,74572,74574,74576,74579],{"class":117,"line":2282},[115,74573,36139],{"class":132},[115,74575,2541],{"class":125},[115,74577,74578],{"class":132},"\"app_file\"",[115,74580,3430],{"class":125},[115,74582,74583,74585,74587,74589],{"class":117,"line":2291},[115,74584,36164],{"class":132},[115,74586,2513],{"class":125},[115,74588,35898],{"class":132},[115,74590,3354],{"class":125},[115,74592,74593],{"class":117,"line":2299},[115,74594,3403],{"class":125},[115,74596,74597,74599],{"class":117,"line":2307},[115,74598,3408],{"class":132},[115,74600,3374],{"class":125},[115,74602,74603,74605],{"class":117,"line":2315},[115,74604,36185],{"class":132},[115,74606,3374],{"class":125},[115,74608,74609,74611,74613,74615],{"class":117,"line":2320},[115,74610,3422],{"class":132},[115,74612,2541],{"class":125},[115,74614,74578],{"class":132},[115,74616,3430],{"class":125},[115,74618,74619,74621,74623,74625],{"class":117,"line":7083},[115,74620,3435],{"class":132},[115,74622,2513],{"class":125},[115,74624,35898],{"class":132},[115,74626,3354],{"class":125},[115,74628,74629,74631,74633,74635],{"class":117,"line":7090},[115,74630,3447],{"class":132},[115,74632,2513],{"class":125},[115,74634,3364],{"class":202},[115,74636,3354],{"class":125},[115,74638,74639],{"class":117,"line":7097},[115,74640,3398],{"class":125},[115,74642,74643,74646],{"class":117,"line":7108},[115,74644,74645],{"class":132},"        \"your_app\"",[115,74647,3374],{"class":125},[115,74649,74650,74652,74654,74656],{"class":117,"line":7113},[115,74651,3422],{"class":132},[115,74653,2541],{"class":125},[115,74655,74578],{"class":132},[115,74657,3430],{"class":125},[115,74659,74660,74662,74664,74666],{"class":117,"line":16535},[115,74661,3435],{"class":132},[115,74663,2513],{"class":125},[115,74665,35898],{"class":132},[115,74667,3354],{"class":125},[115,74669,74670,74672,74674,74676],{"class":117,"line":16544},[115,74671,3447],{"class":132},[115,74673,2513],{"class":125},[115,74675,3364],{"class":202},[115,74677,3354],{"class":125},[115,74679,74680],{"class":117,"line":16549},[115,74681,3398],{"class":125},[115,74683,74684],{"class":117,"line":16555},[115,74685,3403],{"class":125},[115,74687,74688],{"class":117,"line":16564},[115,74689,2323],{"class":125},[16,74691,74692,74694,74695,74697,74698,74700],{},[20,74693,74427],{}," is often a better fit with Linux ",[20,74696,36906],{}," than a plain ",[20,74699,37001],{},", because it notices when the file changes under rotation.",[16,74702,74703],{},"The key rule is to pick one owner for rotation responsibility:",[63,74705,74706,74709],{},[66,74707,74708],{},"either Django rotates internally",[66,74710,74711,74712,74714],{},"or ",[20,74713,36906],{}," rotates the file",[16,74716,74717],{},"Do not use both for the same log file.",[16,74719,17389],{},[63,74721,74722,74725],{},[66,74723,74724],{},"Your actual app logger writes to the file you plan to rotate.",[66,74726,74727],{},"Log messages still appear after a forced rotation.",[23099,74729],{},[52,74731,74733],{"id":74732},"_7-test-the-log-rotation-configuration-safely","7. Test the log rotation configuration safely",[16,74735,74736],{},"Dry-run first:",[106,74738,74740],{"className":108,"code":74739,"language":110,"meta":111,"style":111},"sudo logrotate -d \u002Fetc\u002Flogrotate.d\u002Fyour-app\n",[20,74741,74742],{"__ignoreMap":111},[115,74743,74744,74746,74748,74750],{"class":117,"line":118},[115,74745,2001],{"class":262},[115,74747,36983],{"class":132},[115,74749,1019],{"class":202},[115,74751,74752],{"class":132}," \u002Fetc\u002Flogrotate.d\u002Fyour-app\n",[16,74754,74755],{},"Then force a rotation during a low-risk window:",[106,74757,74759],{"className":108,"code":74758,"language":110,"meta":111,"style":111},"sudo logrotate -f \u002Fetc\u002Flogrotate.d\u002Fyour-app\n",[20,74760,74761],{"__ignoreMap":111},[115,74762,74763,74765,74767,74769],{"class":117,"line":118},[115,74764,2001],{"class":262},[115,74766,36983],{"class":132},[115,74768,2777],{"class":202},[115,74770,74752],{"class":132},[16,74772,74773],{},"Watch Gunicorn service output and log files:",[106,74775,74777],{"className":108,"code":74776,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn.service -n 50 --no-pager\ntail -f \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[20,74778,74779,74796],{"__ignoreMap":111},[115,74780,74781,74783,74785,74787,74790,74792,74794],{"class":117,"line":118},[115,74782,2001],{"class":262},[115,74784,5030],{"class":132},[115,74786,2788],{"class":202},[115,74788,74789],{"class":132}," gunicorn.service",[115,74791,2794],{"class":202},[115,74793,15523],{"class":202},[115,74795,2800],{"class":202},[115,74797,74798,74800,74802],{"class":117,"line":136},[115,74799,36495],{"class":262},[115,74801,2777],{"class":202},[115,74803,74804],{"class":132}," \u002Fvar\u002Flog\u002Fyour-app\u002Fgunicorn-error.log\n",[16,74806,74807],{},"Checks after forcing rotation:",[63,74809,74810,74813,74816,74819,74822],{},[66,74811,74812],{},"rotated files exist",[66,74814,74815],{},"a new active file exists",[66,74817,74818],{},"ownership and mode are correct",[66,74820,74821],{},"Gunicorn keeps writing",[66,74823,74824],{},"no process is writing to a deleted file",[16,74826,74827],{},"Also confirm disk usage:",[106,74829,74831],{"className":108,"code":74830,"language":110,"meta":111,"style":111},"df -h\ndu -sh \u002Fvar\u002Flog\u002Fyour-app\u002F\n",[20,74832,74833,74839],{"__ignoreMap":111},[115,74834,74835,74837],{"class":117,"line":118},[115,74836,50521],{"class":262},[115,74838,50524],{"class":202},[115,74840,74841,74844,74847],{"class":117,"line":136},[115,74842,74843],{"class":262},"du",[115,74845,74846],{"class":202}," -sh",[115,74848,73856],{"class":132},[16,74850,74851],{},"If the rule fails and logging stops, disable the custom rule and restore the active files to a known-good state:",[106,74853,74855],{"className":108,"code":74854,"language":110,"meta":111,"style":111},"sudo mv \u002Fetc\u002Flogrotate.d\u002Fyour-app \u002Froot\u002Fyour-app.logrotate.disabled\nsudo chown appuser:www-data \u002Fvar\u002Flog\u002Fyour-app\u002F*.log\nsudo chmod 0640 \u002Fvar\u002Flog\u002Fyour-app\u002F*.log\nsudo systemctl kill -s USR1 gunicorn.service || sudo systemctl restart gunicorn.service\n",[20,74856,74857,74870,74886,74901],{"__ignoreMap":111},[115,74858,74859,74861,74864,74867],{"class":117,"line":118},[115,74860,2001],{"class":262},[115,74862,74863],{"class":132}," mv",[115,74865,74866],{"class":132}," \u002Fetc\u002Flogrotate.d\u002Fyour-app",[115,74868,74869],{"class":132}," \u002Froot\u002Fyour-app.logrotate.disabled\n",[115,74871,74872,74874,74876,74878,74881,74883],{"class":117,"line":136},[115,74873,2001],{"class":262},[115,74875,6733],{"class":132},[115,74877,74079],{"class":132},[115,74879,74880],{"class":132}," \u002Fvar\u002Flog\u002Fyour-app\u002F",[115,74882,39937],{"class":202},[115,74884,74885],{"class":132},".log\n",[115,74887,74888,74890,74892,74895,74897,74899],{"class":117,"line":149},[115,74889,2001],{"class":262},[115,74891,12480],{"class":132},[115,74893,74894],{"class":202}," 0640",[115,74896,74880],{"class":132},[115,74898,39937],{"class":202},[115,74900,74885],{"class":132},[115,74902,74903,74905,74907,74910,74912,74915,74917,74919,74921,74923,74925],{"class":117,"line":162},[115,74904,2001],{"class":262},[115,74906,3480],{"class":132},[115,74908,74909],{"class":132}," kill",[115,74911,549],{"class":202},[115,74913,74914],{"class":132}," USR1",[115,74916,74789],{"class":132},[115,74918,43235],{"class":121},[115,74920,14228],{"class":262},[115,74922,3480],{"class":132},[115,74924,3483],{"class":132},[115,74926,73924],{"class":132},[16,74928,74929,74930,74932],{},"If your unit name is different, replace ",[20,74931,74235],{}," accordingly. If you changed an existing rotation policy, restore the previous file from backup before the next scheduled rotation.",[23099,74934],{},[52,74936,74938],{"id":74937},"_8-monitor-disk-usage-and-retention","8. Monitor disk usage and retention",[16,74940,74941],{},"Retention depends on debugging needs, compliance, and available disk. A common starting point is 7 to 14 days locally with compression.",[16,74943,74944],{},"Compression reduces space usage, but local rotation is still not centralized logging. If you need long-term search, auditability, or multi-server correlation, ship logs off-server later to a log platform or collector.",[16,74946,74947],{},"Useful periodic checks:",[106,74949,74951],{"className":108,"code":74950,"language":110,"meta":111,"style":111},"sudo logrotate -d \u002Fetc\u002Flogrotate.d\u002Fyour-app\ndf -h\ndu -sh \u002Fvar\u002Flog\u002Fyour-app\u002F\nfind \u002Fvar\u002Flog\u002Fyour-app -type f -name '*.gz' | wc -l\n",[20,74952,74953,74963,74969,74977],{"__ignoreMap":111},[115,74954,74955,74957,74959,74961],{"class":117,"line":118},[115,74956,2001],{"class":262},[115,74958,36983],{"class":132},[115,74960,1019],{"class":202},[115,74962,74752],{"class":132},[115,74964,74965,74967],{"class":117,"line":136},[115,74966,50521],{"class":262},[115,74968,50524],{"class":202},[115,74970,74971,74973,74975],{"class":117,"line":149},[115,74972,74843],{"class":262},[115,74974,74846],{"class":202},[115,74976,73856],{"class":132},[115,74978,74979,74981,74984,74986,74988,74991,74994,74996,74999],{"class":117,"line":162},[115,74980,43899],{"class":262},[115,74982,74983],{"class":132}," \u002Fvar\u002Flog\u002Fyour-app",[115,74985,65150],{"class":202},[115,74987,65179],{"class":132},[115,74989,74990],{"class":202}," -name",[115,74992,74993],{"class":132}," '*.gz'",[115,74995,579],{"class":121},[115,74997,74998],{"class":262}," wc",[115,75000,75001],{"class":202}," -l\n",[23099,75003],{},[52,75005,75007],{"id":75006},"_9-notes-on-automation","9. Notes on automation",[16,75009,75010,75011,75013],{},"Manual ",[20,75012,36906],{}," setup works well for one or two servers. It becomes repetitive when you manage multiple Django apps with the same directory layout, service naming pattern, and retention policy.",[16,75015,75016],{},"Good candidates for reusable scripts or templates are:",[63,75018,75019,75022,75027,75030,75035],{},[66,75020,75021],{},"log directory creation",[66,75023,13812,75024,75026],{},[20,75025,73797],{}," file",[66,75028,75029],{},"Gunicorn systemd logging arguments",[66,75031,75032,75033],{},"post-deploy checks like ",[20,75034,37830],{},[66,75036,75037],{},"ownership and mode validation after rotation",[11,75039,1321],{"id":1320},[16,75041,75042],{},"This setup works because it covers the full lifecycle of file-based production logs:",[63,75044,75045,75050,75055,75060],{},[66,75046,75047,75049],{},[20,75048,36906],{}," controls retention and compression",[66,75051,75052,75054],{},[20,75053,73750],{}," ensures a new writable file exists immediately",[66,75056,75057,75059],{},[20,75058,74336],{}," makes Gunicorn reopen files instead of writing to stale descriptors",[66,75061,75062,75064],{},[20,75063,74427],{}," helps Django detect rotated files cleanly on Linux",[16,75066,75067],{},"Choose alternatives when the architecture is different:",[63,75069,75070,75075,75081],{},[66,75071,63108,75072,75074],{},[20,75073,35702],{},", rely on journal retention instead of file rotation for Gunicorn.",[66,75076,75077,75078,75080],{},"If you run in containers, host ",[20,75079,36906],{}," may not apply to container stdout or stderr logs.",[66,75082,75083,75084,211],{},"If your Django app uses an internal rotating handler, remove that overlap or stop rotating the same file with ",[20,75085,36906],{},[11,75087,1337],{"id":1336},[63,75089,75090,75099,75107,75113,75121,75127,75133,75142],{},[66,75091,75092,75095,75096,75098],{},[1226,75093,75094],{},"Gunicorn keeps writing to the old rotated file:"," your ",[20,75097,73779],{}," signal may be missing, may target the wrong unit, or may not be reaching the Gunicorn master process.",[66,75100,75101,2957,75104,75106],{},[1226,75102,75103],{},"Permission denied after rotation:",[20,75105,73750],{}," owner or group does not match the process user, the parent directory is too restrictive, or a host security policy such as SELinux or AppArmor is blocking writes.",[66,75108,75109,75112],{},[1226,75110,75111],{},"Multiple services share one log file path:"," avoid this. Give each service its own file.",[66,75114,75115,75117,75118,75120],{},[1226,75116,4397],{}," if Gunicorn logs to stdout or stderr, use the container runtime or orchestrator logging configuration instead of host file ",[20,75119,36906],{}," for those streams.",[66,75122,75123,75126],{},[1226,75124,75125],{},"Using journald:"," if Gunicorn is fully journald-based, rotate Django file logs only, or move Django logging there too for consistency.",[66,75128,75129,75132],{},[1226,75130,75131],{},"Disk already full:"," do not blindly delete the active open file. First identify what is open, remove or archive old rotated logs carefully, and confirm the process can reopen the active log path.",[66,75134,75135,75138,75139,75141],{},[1226,75136,75137],{},"Non-systemd deployment:"," if Gunicorn runs under Supervisor, runit, or another process manager, adapt the reopen signal to that environment instead of copying the ",[20,75140,1981],{}," example.",[66,75143,75144,75149],{},[1226,75145,75146,75147,241],{},"Temptation to use ",[20,75148,74370],{}," avoid it unless reopen signaling is impossible, because it can drop log lines during busy periods.",[11,75151,1386],{"id":1385},[16,75153,32206,75154,211],{},[1395,75155,3000],{"href":2999},[16,75157,56930],{},[63,75159,75160,75164],{},[66,75161,75162],{},[1395,75163,2986],{"href":2985},[66,75165,75166],{},[1395,75167,8046],{"href":8045},[16,75169,56948,75170,211],{},[1395,75171,8039],{"href":8038},[11,75173,1420],{"id":1419},[52,75175,75177],{"id":75176},"should-i-rotate-django-logs-with-logrotate-or-python-logging-handlers","Should I rotate Django logs with logrotate or Python logging handlers?",[16,75179,75180,75181,75183,75184,75186],{},"Use one rotation mechanism per file. On Linux, ",[20,75182,36906],{}," plus Django ",[20,75185,74427],{}," is a clean production choice for file-based logs.",[52,75188,75190],{"id":75189},"do-i-need-to-restart-gunicorn-after-log-rotation","Do I need to restart Gunicorn after log rotation?",[16,75192,75193,75194,75196],{},"Usually no. A signal such as ",[20,75195,74336],{}," is often enough to make Gunicorn reopen log files. Verify this behavior on your deployed version, process manager, and unit setup before relying on it.",[52,75198,75200],{"id":75199},"what-retention-period-should-i-use-for-production-logs","What retention period should I use for production logs?",[16,75202,75203],{},"Start with 7 to 14 days on disk, then adjust based on traffic, disk size, debugging needs, and compliance requirements. Compress older files unless you have a strong reason not to.",[52,75205,75207],{"id":75206},"is-journald-better-than-file-based-gunicorn-logging","Is journald better than file-based Gunicorn logging?",[16,75209,75210],{},"It can be simpler on systemd-based hosts because you avoid file rotation for Gunicorn entirely. File logs still make sense when you want explicit app-local files or integration with existing file-based operations.",[1485,75212,75213],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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}",{"title":111,"searchDepth":149,"depth":149,"links":75215},[75216,75217,75218,75229,75230,75231,75232],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":75219},[75220,75221,75222,75223,75224,75225,75226,75227,75228],{"id":73821,"depth":149,"text":73822},{"id":73906,"depth":149,"text":73907},{"id":74041,"depth":149,"text":74042},{"id":74146,"depth":149,"text":74147},{"id":74326,"depth":149,"text":74327},{"id":74417,"depth":149,"text":74418},{"id":74732,"depth":149,"text":74733},{"id":74937,"depth":149,"text":74938},{"id":75006,"depth":149,"text":75007},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":75233},[75234,75235,75236,75237],{"id":75176,"depth":149,"text":75177},{"id":75189,"depth":149,"text":75190},{"id":75199,"depth":149,"text":75200},{"id":75206,"depth":149,"text":75207},"Django and Gunicorn log rotation becomes a production issue as soon as your app writes request logs, error logs, or application logs to disk for more than a few days.",{},"\u002Fdjango-log-rotation-linux",[1551,37876,37877],{"title":73733,"description":75238},[1557,14954,49631],"django-log-rotation-linux",[1557,14954,49631],"TTaOr5QzWI6Xtey7Bec65euOQcy2lSo6l5JQHzrcumo",{"id":75248,"title":75249,"body":75250,"category":3088,"description":76580,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":1546,"meta":76581,"navigation":309,"path":76582,"priority":76583,"related":76584,"role":1553,"section":3098,"seo":76585,"stack":76586,"stem":76587,"tags":76588,"type":1561,"__hash__":76589},"articles\u002Fserve-static-files-whitenoise-django.md","How to Serve Django Static Files with WhiteNoise",{"type":8,"value":75251,"toc":76539},[75252,75254,75260,75263,75266,75268,75271,75317,75322,75324,75328,75331,75345,75348,75359,75362,75366,75369,75383,75387,75390,75469,75474,75501,75509,75513,75515,75529,75532,75536,75539,75572,75575,75579,75591,75595,75601,75658,75661,75676,75679,75690,75693,75697,75699,75713,75719,75723,75732,75735,75749,75752,75799,75802,75818,75824,75828,75833,75846,75849,75853,75856,75860,75863,75865,75879,75882,75885,75889,75892,75896,75899,75902,75914,75917,75923,75952,75955,75981,75983,76005,76011,76015,76022,76227,76231,76264,76268,76301,76303,76307,76313,76317,76324,76327,76331,76334,76342,76344,76350,76352,76356,76362,76366,76372,76376,76379,76407,76411,76417,76421,76424,76435,76438,76440,76446,76455,76464,76466,76470,76473,76480,76486,76490,76492,76520,76524,76527,76531,76537],[11,75253,14],{"id":13},[16,75255,75256,75257,75259],{},"Django does not safely serve static files in production by default. With ",[20,75258,2707],{},", you still need a production path for CSS, JavaScript, fonts, and images collected from your app and dependencies. If that path is missing or misconfigured, the first visible symptom is usually broken styling, including unstyled Django admin pages.",[16,75261,75262],{},"WhiteNoise is a practical option for many Django deployments because it lets the Django application serve pre-collected static files directly. That removes the need for a separate static file mapping in Nginx for simpler setups, especially on small to medium deployments, containerized apps, and single-service environments.",[16,75264,75265],{},"It is not the right tool for everything. WhiteNoise is for versioned static assets that are part of your application release. It is not for user-uploaded media, private files, or very large-scale asset distribution where object storage and a CDN are better fits.",[11,75267,30],{"id":29},[16,75269,75270],{},"To serve Django static files with WhiteNoise in production:",[1173,75272,75273,75278,75287,75293,75298,75303,75308,75311],{},[66,75274,75275,75276],{},"Install ",[20,75277,43557],{},[66,75279,61697,75280,75283,75284],{},[20,75281,75282],{},"WhiteNoiseMiddleware"," directly after ",[20,75285,75286],{},"SecurityMiddleware",[66,75288,49695,75289,3146,75291],{},[20,75290,11908],{},[20,75292,11918],{},[66,75294,49695,75295,75297],{},[20,75296,2719],{}," for production",[66,75299,15164,75300],{},[20,75301,75302],{},"CompressedManifestStaticFilesStorage",[66,75304,42059,75305,75307],{},[20,75306,27195],{}," during build or release",[66,75309,75310],{},"Deploy the new code and restart the app process",[66,75312,75313,75314,75316],{},"Verify that ",[20,75315,11729],{}," assets load and include cache headers",[16,75318,53552,75319,75321],{},[20,75320,2707],{},", and do not route user-uploaded media through WhiteNoise.",[11,75323,43],{"id":42},[52,75325,75327],{"id":75326},"_1-install-whitenoise","1. Install WhiteNoise",[16,75329,75330],{},"Install the package in the same environment used for production:",[106,75332,75334],{"className":108,"code":75333,"language":110,"meta":111,"style":111},"pip install whitenoise\n",[20,75335,75336],{"__ignoreMap":111},[115,75337,75338,75340,75342],{"class":117,"line":118},[115,75339,8618],{"class":262},[115,75341,6600],{"class":132},[115,75343,75344],{"class":132}," whitenoise\n",[16,75346,75347],{},"Pin it in your dependency file:",[106,75349,75353],{"className":75350,"code":75351,"language":75352,"meta":111,"style":111},"language-txt shiki shiki-themes github-light github-dark","whitenoise==6.9.0\n","txt",[20,75354,75355],{"__ignoreMap":111},[115,75356,75357],{"class":117,"line":118},[115,75358,75351],{},[16,75360,75361],{},"If you deploy with Docker, make sure this dependency is installed during image build, not only in local development.",[16,75363,75364],{},[1226,75365,4678],{},[16,75367,75368],{},"Check that the package is available:",[106,75370,75372],{"className":108,"code":75371,"language":110,"meta":111,"style":111},"python -c \"import whitenoise; print(whitenoise.__version__)\"\n",[20,75373,75374],{"__ignoreMap":111},[115,75375,75376,75378,75380],{"class":117,"line":118},[115,75377,1114],{"class":262},[115,75379,1024],{"class":202},[115,75381,75382],{"class":132}," \"import whitenoise; print(whitenoise.__version__)\"\n",[52,75384,75386],{"id":75385},"_2-configure-django-settings-for-static-files","2. Configure Django settings for static files",[16,75388,75389],{},"Your production settings need a public URL path, a collection directory, and valid hostnames:",[106,75391,75393],{"className":2369,"code":75392,"language":1114,"meta":111,"style":111},"from pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = False\nALLOWED_HOSTS = [\"example.com\"]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n",[20,75394,75395,75405,75409,75421,75425,75433,75445,75449,75457],{"__ignoreMap":111},[115,75396,75397,75399,75401,75403],{"class":117,"line":118},[115,75398,5621],{"class":121},[115,75400,16353],{"class":125},[115,75402,5613],{"class":121},[115,75404,16358],{"class":125},[115,75406,75407],{"class":117,"line":136},[115,75408,310],{"emptyLinePlaceholder":309},[115,75410,75411,75413,75415,75417,75419],{"class":117,"line":149},[115,75412,16374],{"class":202},[115,75414,2380],{"class":121},[115,75416,16379],{"class":125},[115,75418,16382],{"class":202},[115,75420,34339],{"class":125},[115,75422,75423],{"class":117,"line":162},[115,75424,310],{"emptyLinePlaceholder":309},[115,75426,75427,75429,75431],{"class":117,"line":175},[115,75428,7350],{"class":202},[115,75430,2380],{"class":121},[115,75432,7355],{"class":202},[115,75434,75435,75437,75439,75441,75443],{"class":117,"line":350},[115,75436,2719],{"class":202},[115,75438,2380],{"class":121},[115,75440,7493],{"class":125},[115,75442,7496],{"class":132},[115,75444,2552],{"class":125},[115,75446,75447],{"class":117,"line":365},[115,75448,310],{"emptyLinePlaceholder":309},[115,75450,75451,75453,75455],{"class":117,"line":380},[115,75452,11908],{"class":202},[115,75454,2380],{"class":121},[115,75456,11913],{"class":132},[115,75458,75459,75461,75463,75465,75467],{"class":117,"line":487},[115,75460,11918],{"class":202},[115,75462,2380],{"class":121},[115,75464,11923],{"class":202},[115,75466,11926],{"class":121},[115,75468,11929],{"class":132},[16,75470,75471,75472,241],{},"If your project also has source static directories, keep them in ",[20,75473,42242],{},[106,75475,75477],{"className":2369,"code":75476,"language":1114,"meta":111,"style":111},"STATICFILES_DIRS = [\n    BASE_DIR \u002F \"static\",\n]\n",[20,75478,75479,75487,75497],{"__ignoreMap":111},[115,75480,75481,75483,75485],{"class":117,"line":118},[115,75482,42242],{"class":202},[115,75484,2380],{"class":121},[115,75486,3540],{"class":125},[115,75488,75489,75491,75493,75495],{"class":117,"line":136},[115,75490,42251],{"class":202},[115,75492,11926],{"class":121},[115,75494,42256],{"class":132},[115,75496,3354],{"class":125},[115,75498,75499],{"class":117,"line":149},[115,75500,2552],{"class":125},[16,75502,75503,75505,75506,75508],{},[20,75504,11918],{}," is where ",[20,75507,13689],{}," writes the final deployment-ready files. It should not be the same directory as your source static files.",[16,75510,75511],{},[1226,75512,4678],{},[16,75514,33361],{},[106,75516,75517],{"className":108,"code":42307,"language":110,"meta":111,"style":111},[20,75518,75519],{"__ignoreMap":111},[115,75520,75521,75523,75525,75527],{"class":117,"line":118},[115,75522,1114],{"class":262},[115,75524,1117],{"class":132},[115,75526,42318],{"class":132},[115,75528,42321],{"class":132},[16,75530,75531],{},"You should see at least one resolved path. If not, your static files configuration is incomplete before WhiteNoise is even involved.",[52,75533,75535],{"id":75534},"_3-add-whitenoise-middleware","3. Add WhiteNoise middleware",[16,75537,75538],{},"Add the middleware directly after Django’s security middleware:",[106,75540,75542],{"className":2369,"code":75541,"language":1114,"meta":111,"style":111},"MIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    # other middleware...\n]\n",[20,75543,75544,75552,75558,75564,75568],{"__ignoreMap":111},[115,75545,75546,75548,75550],{"class":117,"line":118},[115,75547,16617],{"class":202},[115,75549,2380],{"class":121},[115,75551,3540],{"class":125},[115,75553,75554,75556],{"class":117,"line":136},[115,75555,16627],{"class":132},[115,75557,3354],{"class":125},[115,75559,75560,75562],{"class":117,"line":149},[115,75561,16635],{"class":132},[115,75563,3354],{"class":125},[115,75565,75566],{"class":117,"line":162},[115,75567,62174],{"class":3861},[115,75569,75570],{"class":117,"line":175},[115,75571,2552],{"class":125},[16,75573,75574],{},"Order matters. WhiteNoise should be near the top so it can handle static file requests early and efficiently.",[16,75576,75577],{},[1226,75578,4678],{},[16,75580,75581,75582,75584,75585,75587,75588,75590],{},"Start the app with ",[20,75583,2707],{}," and request a known static path after running ",[20,75586,13689],{},". If static files only work when ",[20,75589,24957],{},", the production setup is still wrong.",[52,75592,75594],{"id":75593},"_4-enable-hashed-and-compressed-static-file-storage","4. Enable hashed and compressed static file storage",[16,75596,75597,75598,75600],{},"For current Django versions, use the ",[20,75599,16659],{}," setting:",[106,75602,75604],{"className":2369,"code":75603,"language":1114,"meta":111,"style":111},"STORAGES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.files.storage.FileSystemStorage\",\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    },\n}\n",[20,75605,75606,75614,75620,75630,75634,75640,75650,75654],{"__ignoreMap":111},[115,75607,75608,75610,75612],{"class":117,"line":118},[115,75609,16659],{"class":202},[115,75611,2380],{"class":121},[115,75613,2166],{"class":125},[115,75615,75616,75618],{"class":117,"line":136},[115,75617,10664],{"class":132},[115,75619,3374],{"class":125},[115,75621,75622,75624,75626,75628],{"class":117,"line":149},[115,75623,16677],{"class":132},[115,75625,2513],{"class":125},[115,75627,42862],{"class":132},[115,75629,3354],{"class":125},[115,75631,75632],{"class":117,"line":162},[115,75633,3403],{"class":125},[115,75635,75636,75638],{"class":117,"line":175},[115,75637,16669],{"class":132},[115,75639,3374],{"class":125},[115,75641,75642,75644,75646,75648],{"class":117,"line":350},[115,75643,16677],{"class":132},[115,75645,2513],{"class":125},[115,75647,16682],{"class":132},[115,75649,3354],{"class":125},[115,75651,75652],{"class":117,"line":365},[115,75653,3403],{"class":125},[115,75655,75656],{"class":117,"line":380},[115,75657,2323],{"class":125},[16,75659,75660],{},"For older Django versions, use:",[106,75662,75664],{"className":2369,"code":75663,"language":1114,"meta":111,"style":111},"STATICFILES_STORAGE = \"whitenoise.storage.CompressedManifestStaticFilesStorage\"\n",[20,75665,75666],{"__ignoreMap":111},[115,75667,75668,75671,75673],{"class":117,"line":118},[115,75669,75670],{"class":202},"STATICFILES_STORAGE",[115,75672,2380],{"class":121},[115,75674,75675],{"class":132}," \"whitenoise.storage.CompressedManifestStaticFilesStorage\"\n",[16,75677,75678],{},"This storage backend does two important things:",[63,75680,75681,75687],{},[66,75682,75683,75684],{},"creates hashed filenames such as ",[20,75685,75686],{},"app.4f3c1d2a.css",[66,75688,75689],{},"generates compressed versions when appropriate",[16,75691,75692],{},"Hashed filenames make long-term caching safe because browsers fetch a new file when the content changes.",[16,75694,75695],{},[1226,75696,4678],{},[16,75698,33361],{},[106,75700,75701],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,75702,75703],{"__ignoreMap":111},[115,75704,75705,75707,75709,75711],{"class":117,"line":118},[115,75706,1114],{"class":262},[115,75708,1117],{"class":132},[115,75710,1838],{"class":132},[115,75712,1841],{"class":202},[16,75714,75715,75716,75718],{},"Then inspect the output in ",[20,75717,11918],{},". You should see fingerprinted files and a manifest file.",[52,75720,75722],{"id":75721},"_5-run-collectstatic-during-deployment","5. Run collectstatic during deployment",[16,75724,75725,75726,75728,75729,75731],{},"WhiteNoise serves files that already exist in ",[20,75727,11918],{},". That means ",[20,75730,13689],{}," must run as part of your deployment workflow.",[16,75733,75734],{},"Non-Docker release step:",[106,75736,75737],{"className":108,"code":32756,"language":110,"meta":111,"style":111},[20,75738,75739],{"__ignoreMap":111},[115,75740,75741,75743,75745,75747],{"class":117,"line":118},[115,75742,1114],{"class":262},[115,75744,1117],{"class":132},[115,75746,1838],{"class":132},[115,75748,1841],{"class":202},[16,75750,75751],{},"Example with systemd and Gunicorn release flow:",[106,75753,75755],{"className":108,"code":75754,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npython manage.py migrate --noinput\npython manage.py collectstatic --noinput\nsudo systemctl restart gunicorn\n",[20,75756,75757,75763,75769,75779,75789],{"__ignoreMap":111},[115,75758,75759,75761],{"class":117,"line":118},[115,75760,5303],{"class":202},[115,75762,5306],{"class":132},[115,75764,75765,75767],{"class":117,"line":136},[115,75766,5311],{"class":202},[115,75768,5314],{"class":132},[115,75770,75771,75773,75775,75777],{"class":117,"line":149},[115,75772,1114],{"class":262},[115,75774,1117],{"class":132},[115,75776,1826],{"class":132},[115,75778,1841],{"class":202},[115,75780,75781,75783,75785,75787],{"class":117,"line":162},[115,75782,1114],{"class":262},[115,75784,1117],{"class":132},[115,75786,1838],{"class":132},[115,75788,1841],{"class":202},[115,75790,75791,75793,75795,75797],{"class":117,"line":175},[115,75792,2001],{"class":262},[115,75794,3480],{"class":132},[115,75796,3483],{"class":132},[115,75798,1987],{"class":132},[16,75800,75801],{},"Dockerfile example:",[106,75803,75805],{"className":16832,"code":75804,"language":16834,"meta":111,"style":111},"# Only if your build has access to the project code, settings, and required env\nRUN python manage.py collectstatic --noinput\n",[20,75806,75807,75812],{"__ignoreMap":111},[115,75808,75809],{"class":117,"line":118},[115,75810,75811],{"class":3861},"# Only if your build has access to the project code, settings, and required env\n",[115,75813,75814,75816],{"class":117,"line":136},[115,75815,16905],{"class":121},[115,75817,16930],{"class":125},[16,75819,75820,75821,75823],{},"In Docker deployments, running ",[20,75822,13689],{}," at image build time is usually more predictable than doing it at container startup, provided the build environment can import Django settings safely. Otherwise, use a dedicated release step.",[16,75825,75826],{},[1226,75827,4678],{},[16,75829,39827,75830,75832],{},[20,75831,13689],{},", confirm files exist:",[106,75834,75836],{"className":108,"code":75835,"language":110,"meta":111,"style":111},"ls -la staticfiles\u002F\n",[20,75837,75838],{"__ignoreMap":111},[115,75839,75840,75842,75844],{"class":117,"line":118},[115,75841,532],{"class":262},[115,75843,29638],{"class":202},[115,75845,12219],{"class":132},[16,75847,75848],{},"If the command fails, do not continue the deployment. A failed manifest build can break asset references in production.",[16,75850,75851],{},[1226,75852,4956],{},[16,75854,75855],{},"If a release includes template or frontend changes, roll back code and static assets together. Do not keep old code with new static manifest files or vice versa.",[52,75857,75859],{"id":75858},"_6-deploy-and-restart-the-app-process","6. Deploy and restart the app process",[16,75861,75862],{},"After static files are collected, deploy the updated release and restart the app service if your process manager requires it.",[16,75864,12414],{},[106,75866,75867],{"className":108,"code":3471,"language":110,"meta":111,"style":111},[20,75868,75869],{"__ignoreMap":111},[115,75870,75871,75873,75875,75877],{"class":117,"line":118},[115,75872,2001],{"class":262},[115,75874,3480],{"class":132},[115,75876,3483],{"class":132},[115,75878,1987],{"class":132},[16,75880,75881],{},"Or perform a controlled container rollout through your deployment tool.",[16,75883,75884],{},"The important point is that the running application and the collected static manifest belong to the same release. In multi-instance deployments, avoid serving mixed old and new releases behind the load balancer longer than necessary.",[16,75886,75887],{},[1226,75888,4678],{},[16,75890,75891],{},"Check that the app starts cleanly and that requests do not raise missing manifest entry errors in logs or error reporting.",[52,75893,75895],{"id":75894},"_7-verify-static-files-in-production","7. Verify static files in production",[16,75897,75898],{},"Use a known asset first. Django admin CSS is a good target because it is present on most projects.",[16,75900,75901],{},"Check headers:",[106,75903,75904],{"className":108,"code":13394,"language":110,"meta":111,"style":111},[20,75905,75906],{"__ignoreMap":111},[115,75907,75908,75910,75912],{"class":117,"line":118},[115,75909,2764],{"class":262},[115,75911,2767],{"class":202},[115,75913,13405],{"class":132},[16,75915,75916],{},"You should see a successful response and appropriate content type. With hashed assets, you should also see cache-friendly headers.",[16,75918,75919,75920,75922],{},"To test a fingerprinted file, use an actual generated filename from ",[20,75921,11918],{}," or from page source. For example:",[106,75924,75926],{"className":108,"code":75925,"language":110,"meta":111,"style":111},"find staticfiles -type f | grep -E '\\.[0-9a-f]{8,}\\.(css|js)$' | head\n",[20,75927,75928],{"__ignoreMap":111},[115,75929,75930,75932,75935,75937,75939,75941,75943,75945,75948,75950],{"class":117,"line":118},[115,75931,43899],{"class":262},[115,75933,75934],{"class":132}," staticfiles",[115,75936,65150],{"class":202},[115,75938,65179],{"class":132},[115,75940,579],{"class":121},[115,75942,4838],{"class":262},[115,75944,6482],{"class":202},[115,75946,75947],{"class":132}," '\\.[0-9a-f]{8,}\\.(css|js)$'",[115,75949,579],{"class":121},[115,75951,582],{"class":262},[16,75953,75954],{},"Then test one real generated file:",[106,75956,75958],{"className":108,"code":75957,"language":110,"meta":111,"style":111},"curl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fpath\u002Fto\u002Fgenerated-file.\u003Chash>.css\n",[20,75959,75960],{"__ignoreMap":111},[115,75961,75962,75964,75966,75969,75971,75974,75976,75978],{"class":117,"line":118},[115,75963,2764],{"class":262},[115,75965,2767],{"class":202},[115,75967,75968],{"class":132}," https:\u002F\u002Fexample.com\u002Fstatic\u002Fpath\u002Fto\u002Fgenerated-file.",[115,75970,22810],{"class":121},[115,75972,75973],{"class":132},"has",[115,75975,44024],{"class":125},[115,75977,22818],{"class":121},[115,75979,75980],{"class":132},".css\n",[16,75982,42265],{},[63,75984,75985,75990,75996,75999,76002],{},[66,75986,75987,75988],{},"the file returns ",[20,75989,42150],{},[66,75991,75992,75995],{},[20,75993,75994],{},"Content-Type"," matches the asset type",[66,75997,75998],{},"cache headers are present",[66,76000,76001],{},"admin pages load with styling",[66,76003,76004],{},"application logs do not show repeated 404s for static assets",[16,76006,76007,76008,76010],{},"If you are behind Nginx or Caddy, verify the reverse proxy is forwarding requests correctly and not intercepting ",[20,76009,11729],{}," with a broken local path rule. WhiteNoise does not replace correct proxy, HTTPS, or upstream configuration.",[11,76012,76014],{"id":76013},"configcode-examples","Config\u002Fcode examples",[52,76016,76018,76019,76021],{"id":76017},"minimal-settingspy-changes","Minimal ",[20,76020,10342],{}," changes",[106,76023,76025],{"className":2369,"code":76024,"language":1114,"meta":111,"style":111},"from pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = False\nALLOWED_HOSTS = [\"example.com\"]\n\nMIDDLEWARE = [\n    \"django.middleware.security.SecurityMiddleware\",\n    \"whitenoise.middleware.WhiteNoiseMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n]\n\nSTATIC_URL = \"\u002Fstatic\u002F\"\nSTATIC_ROOT = BASE_DIR \u002F \"staticfiles\"\n\nSTORAGES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.files.storage.FileSystemStorage\",\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"whitenoise.storage.CompressedManifestStaticFilesStorage\",\n    },\n}\n",[20,76026,76027,76037,76041,76053,76057,76065,76077,76081,76089,76095,76101,76108,76115,76122,76129,76136,76143,76147,76151,76159,76171,76175,76183,76189,76199,76203,76209,76219,76223],{"__ignoreMap":111},[115,76028,76029,76031,76033,76035],{"class":117,"line":118},[115,76030,5621],{"class":121},[115,76032,16353],{"class":125},[115,76034,5613],{"class":121},[115,76036,16358],{"class":125},[115,76038,76039],{"class":117,"line":136},[115,76040,310],{"emptyLinePlaceholder":309},[115,76042,76043,76045,76047,76049,76051],{"class":117,"line":149},[115,76044,16374],{"class":202},[115,76046,2380],{"class":121},[115,76048,16379],{"class":125},[115,76050,16382],{"class":202},[115,76052,34339],{"class":125},[115,76054,76055],{"class":117,"line":162},[115,76056,310],{"emptyLinePlaceholder":309},[115,76058,76059,76061,76063],{"class":117,"line":175},[115,76060,7350],{"class":202},[115,76062,2380],{"class":121},[115,76064,7355],{"class":202},[115,76066,76067,76069,76071,76073,76075],{"class":117,"line":350},[115,76068,2719],{"class":202},[115,76070,2380],{"class":121},[115,76072,7493],{"class":125},[115,76074,7496],{"class":132},[115,76076,2552],{"class":125},[115,76078,76079],{"class":117,"line":365},[115,76080,310],{"emptyLinePlaceholder":309},[115,76082,76083,76085,76087],{"class":117,"line":380},[115,76084,16617],{"class":202},[115,76086,2380],{"class":121},[115,76088,3540],{"class":125},[115,76090,76091,76093],{"class":117,"line":487},[115,76092,16627],{"class":132},[115,76094,3354],{"class":125},[115,76096,76097,76099],{"class":117,"line":2095},[115,76098,16635],{"class":132},[115,76100,3354],{"class":125},[115,76102,76103,76106],{"class":117,"line":2104},[115,76104,76105],{"class":132},"    \"django.contrib.sessions.middleware.SessionMiddleware\"",[115,76107,3354],{"class":125},[115,76109,76110,76113],{"class":117,"line":2113},[115,76111,76112],{"class":132},"    \"django.middleware.common.CommonMiddleware\"",[115,76114,3354],{"class":125},[115,76116,76117,76120],{"class":117,"line":2122},[115,76118,76119],{"class":132},"    \"django.middleware.csrf.CsrfViewMiddleware\"",[115,76121,3354],{"class":125},[115,76123,76124,76127],{"class":117,"line":2131},[115,76125,76126],{"class":132},"    \"django.contrib.auth.middleware.AuthenticationMiddleware\"",[115,76128,3354],{"class":125},[115,76130,76131,76134],{"class":117,"line":2136},[115,76132,76133],{"class":132},"    \"django.contrib.messages.middleware.MessageMiddleware\"",[115,76135,3354],{"class":125},[115,76137,76138,76141],{"class":117,"line":2142},[115,76139,76140],{"class":132},"    \"django.middleware.clickjacking.XFrameOptionsMiddleware\"",[115,76142,3354],{"class":125},[115,76144,76145],{"class":117,"line":2273},[115,76146,2552],{"class":125},[115,76148,76149],{"class":117,"line":2282},[115,76150,310],{"emptyLinePlaceholder":309},[115,76152,76153,76155,76157],{"class":117,"line":2291},[115,76154,11908],{"class":202},[115,76156,2380],{"class":121},[115,76158,11913],{"class":132},[115,76160,76161,76163,76165,76167,76169],{"class":117,"line":2299},[115,76162,11918],{"class":202},[115,76164,2380],{"class":121},[115,76166,11923],{"class":202},[115,76168,11926],{"class":121},[115,76170,11929],{"class":132},[115,76172,76173],{"class":117,"line":2307},[115,76174,310],{"emptyLinePlaceholder":309},[115,76176,76177,76179,76181],{"class":117,"line":2315},[115,76178,16659],{"class":202},[115,76180,2380],{"class":121},[115,76182,2166],{"class":125},[115,76184,76185,76187],{"class":117,"line":2320},[115,76186,10664],{"class":132},[115,76188,3374],{"class":125},[115,76190,76191,76193,76195,76197],{"class":117,"line":7083},[115,76192,16677],{"class":132},[115,76194,2513],{"class":125},[115,76196,42862],{"class":132},[115,76198,3354],{"class":125},[115,76200,76201],{"class":117,"line":7090},[115,76202,3403],{"class":125},[115,76204,76205,76207],{"class":117,"line":7097},[115,76206,16669],{"class":132},[115,76208,3374],{"class":125},[115,76210,76211,76213,76215,76217],{"class":117,"line":7108},[115,76212,16677],{"class":132},[115,76214,2513],{"class":125},[115,76216,16682],{"class":132},[115,76218,3354],{"class":125},[115,76220,76221],{"class":117,"line":7113},[115,76222,3403],{"class":125},[115,76224,76225],{"class":117,"line":16535},[115,76226,2323],{"class":125},[52,76228,76230],{"id":76229},"release-workflow-example","Release workflow example",[106,76232,76234],{"className":108,"code":76233,"language":110,"meta":111,"style":111},"python manage.py migrate --noinput\npython manage.py collectstatic --noinput\nsystemctl restart gunicorn\n",[20,76235,76236,76246,76256],{"__ignoreMap":111},[115,76237,76238,76240,76242,76244],{"class":117,"line":118},[115,76239,1114],{"class":262},[115,76241,1117],{"class":132},[115,76243,1826],{"class":132},[115,76245,1841],{"class":202},[115,76247,76248,76250,76252,76254],{"class":117,"line":136},[115,76249,1114],{"class":262},[115,76251,1117],{"class":132},[115,76253,1838],{"class":132},[115,76255,1841],{"class":202},[115,76257,76258,76260,76262],{"class":117,"line":149},[115,76259,1981],{"class":262},[115,76261,3483],{"class":132},[115,76263,1987],{"class":132},[52,76265,76267],{"id":76266},"validation-commands","Validation commands",[106,76269,76271],{"className":108,"code":76270,"language":110,"meta":111,"style":111},"python manage.py collectstatic --noinput\npython manage.py findstatic admin\u002Fcss\u002Fbase.css\ncurl -I https:\u002F\u002Fexample.com\u002Fstatic\u002Fadmin\u002Fcss\u002Fbase.css\n",[20,76272,76273,76283,76293],{"__ignoreMap":111},[115,76274,76275,76277,76279,76281],{"class":117,"line":118},[115,76276,1114],{"class":262},[115,76278,1117],{"class":132},[115,76280,1838],{"class":132},[115,76282,1841],{"class":202},[115,76284,76285,76287,76289,76291],{"class":117,"line":136},[115,76286,1114],{"class":262},[115,76288,1117],{"class":132},[115,76290,42318],{"class":132},[115,76292,42321],{"class":132},[115,76294,76295,76297,76299],{"class":117,"line":149},[115,76296,2764],{"class":262},[115,76298,2767],{"class":202},[115,76300,13405],{"class":132},[11,76302,1321],{"id":1320},[52,76304,76306],{"id":76305},"how-whitenoise-works-in-production","How WhiteNoise works in production",[16,76308,76309,76310,76312],{},"WhiteNoise serves static assets from the collected output directory through your Django app process. Instead of configuring a separate web server path for static files, you let the app return those files directly. This is why ",[20,76311,13689],{}," is required: WhiteNoise serves the built static output, not your source static directories.",[52,76314,76316],{"id":76315},"why-manifest-storage-improves-reliability","Why manifest storage improves reliability",[16,76318,76319,76320,76323],{},"Manifest storage rewrites references to use hashed filenames. That prevents stale browser caches after a deployment. Without hashing, a browser may keep using an old ",[20,76321,76322],{},"app.css"," even though the new release expects different asset contents.",[16,76325,76326],{},"This also makes rollback safer when your release process is atomic. Each release carries a matching set of templates, code, and static asset names.",[52,76328,76330],{"id":76329},"security-and-performance-notes","Security and performance notes",[16,76332,76333],{},"WhiteNoise is for public static assets only. Do not use it for user-uploaded media, private reports, or access-controlled files. Those need a different storage and delivery path.",[16,76335,53552,76336,76338,76339,76341],{},[20,76337,2707],{}," in production and set ",[20,76340,2719],{}," correctly for the deployed domain names. If you run behind Nginx or Caddy, let the proxy handle HTTPS termination and upstream forwarding while WhiteNoise handles static asset responses inside the app. WhiteNoise is not a substitute for correct reverse proxy or HTTPS settings.",[52,76343,10084],{"id":10083},[16,76345,76346,76347,76349],{},"If you deploy more than one environment or release frequently, this manual flow should become a script or template. The repeatable parts are dependency pinning, middleware insertion, ",[20,76348,13689],{},", service restart, and post-deploy asset checks. Automating those steps reduces the chance of broken admin CSS or mismatched manifests during release.",[11,76351,10095],{"id":10094},[52,76353,76355],{"id":76354},"whitenoise-with-docker","WhiteNoise with Docker",[16,76357,76358,76359,76361],{},"In Docker, prefer running ",[20,76360,13689],{}," during image build only when the build can safely import Django settings and has the required environment. Otherwise, run it in a dedicated release job. Avoid doing it independently in every container on startup, especially with multiple replicas.",[52,76363,76365],{"id":76364},"whitenoise-behind-nginx-or-caddy","WhiteNoise behind Nginx or Caddy",[16,76367,76368,76369,76371],{},"You can still use WhiteNoise behind a reverse proxy. Nginx or Caddy handles TLS and proxies requests to Gunicorn or Uvicorn. WhiteNoise can simplify the static file side by removing the need for a separate ",[20,76370,11729],{}," filesystem mapping.",[52,76373,76375],{"id":76374},"whitenoise-with-django-admin","WhiteNoise with Django admin",[16,76377,76378],{},"If the Django admin is missing CSS, check these first:",[63,76380,76381,76386,76391,76396,76399,76404],{},[66,76382,76383,76385],{},[20,76384,13689],{}," actually ran",[66,76387,76388,76390],{},[20,76389,11918],{}," exists and contains files",[66,76392,76393,76395],{},[20,76394,75282],{}," is in the correct position",[66,76397,76398],{},"manifest storage is configured correctly",[66,76400,76401,76403],{},[20,76402,2719],{}," is valid and the app is actually serving requests",[66,76405,76406],{},"the deployed release and manifest match",[52,76408,76410],{"id":76409},"handling-rollback-safely","Handling rollback safely",[16,76412,76413,76414,76416],{},"Rollback should restore both application code and the matching static assets. If your templates reference ",[20,76415,75686],{}," but the rollback only restores code and not static files, users may get 404s for hashed asset paths. Immutable images or atomic release directories help avoid this mismatch.",[52,76418,76420],{"id":76419},"what-whitenoise-does-not-handle","What WhiteNoise does not handle",[16,76422,76423],{},"WhiteNoise does not handle:",[63,76425,76426,76429,76432],{},[66,76427,76428],{},"user-uploaded media files",[66,76430,76431],{},"private file delivery",[66,76433,76434],{},"large-scale CDN or object-storage architecture by itself",[16,76436,76437],{},"If you need those, use a separate media storage design.",[11,76439,1386],{"id":1385},[16,76441,76442,76443,211],{},"For the underlying concepts, see ",[1395,76444,76445],{"href":2978},"Django static files in production: collectstatic, STATIC_ROOT, and STATIC_URL explained",[16,76447,76448,76449,1153,76451,20346,76453,211],{},"If you are comparing deployment architectures, see ",[1395,76450,2986],{"href":2985},[1395,76452,8039],{"href":8038},[1395,76454,8046],{"href":8045},[16,76456,76457,76458,76461,76462,211],{},"If static assets still fail after deployment, use ",[1395,76459,76460],{"href":4551},"why Django static files are not loading in production"," as a troubleshooting checklist, and review the broader ",[1395,76463,32365],{"href":2999},[11,76465,1420],{"id":1419},[52,76467,76469],{"id":76468},"can-whitenoise-replace-nginx-for-serving-django-static-files","Can WhiteNoise replace Nginx for serving Django static files?",[16,76471,76472],{},"For many small and medium Django deployments, yes, WhiteNoise can serve static files without a separate Nginx static file mapping. But Nginx may still be useful for TLS termination, reverse proxying, buffering, and broader traffic handling.",[52,76474,76476,76477,76479],{"id":76475},"do-i-still-need-collectstatic-when-using-whitenoise","Do I still need ",[20,76478,13689],{}," when using WhiteNoise?",[16,76481,76482,76483,76485],{},"Yes. WhiteNoise serves the files produced by ",[20,76484,13689],{},". If you skip that step, your production app will not have the complete static asset set or manifest.",[52,76487,76489],{"id":76488},"why-is-the-django-admin-missing-css-after-deployment","Why is the Django admin missing CSS after deployment?",[16,76491,24862],{},[63,76493,76494,76499,76504,76509,76512,76517],{},[66,76495,76496,76498],{},[20,76497,13689],{}," did not run",[66,76500,76501,76503],{},[20,76502,11918],{}," is incorrect",[66,76505,76506,76508],{},[20,76507,75282],{}," is missing or ordered incorrectly",[66,76510,76511],{},"manifest storage is misconfigured",[66,76513,76514,76516],{},[20,76515,2719],{}," or proxy routing prevents the app from serving requests correctly",[66,76518,76519],{},"the release restarted with code that does not match the current static manifest",[52,76521,76523],{"id":76522},"should-i-use-whitenoise-for-user-uploaded-media-files","Should I use WhiteNoise for user-uploaded media files?",[16,76525,76526],{},"No. WhiteNoise is for static assets that are part of your application release. User-uploaded media should use a separate storage and serving path.",[52,76528,76530],{"id":76529},"is-whitenoise-suitable-for-docker-deployments","Is WhiteNoise suitable for Docker deployments?",[16,76532,76533,76534,76536],{},"Yes. It works well in Docker if you run ",[20,76535,13689],{}," during image build only when the build environment can import Django settings safely, or in a controlled release step that produces a matching image and static asset set.",[1485,76538,8220],{},{"title":111,"searchDepth":149,"depth":149,"links":76540},[76541,76542,76543,76552,76558,76564,76571,76572],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":76544},[76545,76546,76547,76548,76549,76550,76551],{"id":75326,"depth":149,"text":75327},{"id":75385,"depth":149,"text":75386},{"id":75534,"depth":149,"text":75535},{"id":75593,"depth":149,"text":75594},{"id":75721,"depth":149,"text":75722},{"id":75858,"depth":149,"text":75859},{"id":75894,"depth":149,"text":75895},{"id":76013,"depth":136,"text":76014,"children":76553},[76554,76556,76557],{"id":76017,"depth":149,"text":76555},"Minimal settings.py changes",{"id":76229,"depth":149,"text":76230},{"id":76266,"depth":149,"text":76267},{"id":1320,"depth":136,"text":1321,"children":76559},[76560,76561,76562,76563],{"id":76305,"depth":149,"text":76306},{"id":76315,"depth":149,"text":76316},{"id":76329,"depth":149,"text":76330},{"id":10083,"depth":149,"text":10084},{"id":10094,"depth":136,"text":10095,"children":76565},[76566,76567,76568,76569,76570],{"id":76354,"depth":149,"text":76355},{"id":76364,"depth":149,"text":76365},{"id":76374,"depth":149,"text":76375},{"id":76409,"depth":149,"text":76410},{"id":76419,"depth":149,"text":76420},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":76573},[76574,76575,76577,76578,76579],{"id":76468,"depth":149,"text":76469},{"id":76475,"depth":149,"text":76576},"Do I still need collectstatic when using WhiteNoise?",{"id":76488,"depth":149,"text":76489},{"id":76522,"depth":149,"text":76523},{"id":76529,"depth":149,"text":76530},"Django does not safely serve static files in production by default. With DEBUG=False, you still need a production path for CSS, JavaScript, fonts, and images collected from your...",{},"\u002Fserve-static-files-whitenoise-django","18",[2978,20258,11637],{"title":75249,"description":76580},[1557,43557],"serve-static-files-whitenoise-django",[1557,43557],"bY0fgZ-RKKRsL8l0Axe40I8JYNtvMn4vv1PUDrA9z0U",{"id":76591,"title":76592,"body":76593,"category":1541,"description":78245,"difficulty":23035,"extension":1544,"funnel_stage":23036,"intent":1546,"meta":78246,"navigation":309,"path":78247,"priority":24939,"related":78248,"role":23035,"section":1554,"seo":78249,"stack":78250,"stem":78251,"tags":78252,"type":1561,"__hash__":78253},"articles\u002Fzero-downtime-django-deployment.md","How to Set Up Zero-Downtime Deployments for Django",{"type":8,"value":76594,"toc":78201},[76595,76597,76600,76607,76621,76628,76630,76633,76653,76668,76670,76674,76678,76681,76684,76695,76698,76709,76712,76716,76719,76727,76730,76733,76744,76747,76751,76754,76757,76774,76777,76781,76785,76788,76867,76870,76872,76887,76894,76898,76901,76967,76973,76976,76991,76995,77001,77012,77015,77026,77029,77033,77036,77078,77081,77103,77106,77110,77113,77116,77127,77131,77135,77137,77154,77157,77186,77189,77236,77239,77283,77286,77290,77293,77296,77377,77380,77487,77490,77522,77526,77529,77532,77648,77650,77672,77674,77686,77689,77706,77709,77711,77724,77728,77732,77738,77743,77844,77848,77880,77883,77886,77894,77898,77901,77904,77924,77927,77930,77934,77938,77941,77954,77957,77968,77972,77974,77985,77988,77992,77995,78029,78032,78034,78037,78040,78060,78063,78065,78116,78120,78123,78125,78133,78140,78147,78154,78161,78163,78167,78170,78174,78177,78181,78184,78188,78191,78195,78198],[11,76596,14],{"id":13},[16,76598,76599],{},"A standard Django deploy often causes a brief outage because the old app process stops before the new one is fully ready. That usually shows up as dropped requests, a burst of 502\u002F503 errors from Nginx, failed admin logins, or users hitting code that expects a database schema that is not ready yet.",[16,76601,76602,76603,76606],{},"In practice, ",[1226,76604,76605],{},"zero downtime Django deployment"," means:",[63,76608,76609,76612,76615,76618],{},[66,76610,76611],{},"existing HTTP traffic is not interrupted during the release",[66,76613,76614],{},"old workers finish in-flight requests cleanly",[66,76616,76617],{},"new workers pass readiness checks before receiving traffic",[66,76619,76620],{},"database changes stay compatible across old and new code during the transition",[16,76622,76623,76624,76627],{},"The hardest part is usually not Gunicorn or Nginx. It is ",[1226,76625,76626],{},"database migration compatibility",". If a release removes or renames a column that old code still uses, the deploy is no longer zero-downtime even if the process restart is graceful.",[11,76629,30],{"id":29},[16,76631,76632],{},"The safest path is:",[1173,76634,76635,76638,76641,76644,76647,76650],{},[66,76636,76637],{},"keep the old app instances serving traffic",[66,76639,76640],{},"start the new version in parallel",[66,76642,76643],{},"verify it with a health check",[66,76645,76646],{},"switch traffic only after it is ready",[66,76648,76649],{},"use backward-compatible migrations",[66,76651,76652],{},"keep a fast rollback path",[16,76654,76655,76656,76659,76660,76663,76664,76667],{},"For a single-host setup, the simplest method is usually ",[1226,76657,76658],{},"Gunicorn behind Nginx with release directories and a controlled port switch",". For easier rollback, use ",[1226,76661,76662],{},"blue-green deployment",". For container-based stacks, run ",[1226,76665,76666],{},"multiple app instances behind a health-aware reverse proxy or orchestrator"," and replace versions gradually.",[11,76669,43],{"id":42},[11,76671,76673],{"id":76672},"choose-a-zero-downtime-deployment-strategy-for-django","Choose a zero-downtime deployment strategy for Django",[52,76675,76677],{"id":76676},"_1-gunicorn-port-switch-behind-nginx","1) Gunicorn port switch behind Nginx",[16,76679,76680],{},"Use this when you run Django on one Linux host with systemd, Gunicorn, and Nginx.",[16,76682,76683],{},"Why it works:",[63,76685,76686,76689,76692],{},[66,76687,76688],{},"Nginx stays up the whole time",[66,76690,76691],{},"the new app instance starts before traffic moves",[66,76693,76694],{},"existing requests can finish on the old instance during cutover",[16,76696,76697],{},"Tradeoffs:",[63,76699,76700,76703,76706],{},[66,76701,76702],{},"rollback is slower than blue-green",[66,76704,76705],{},"migration mistakes still break the release",[66,76707,76708],{},"single-host capacity limits remain",[16,76710,76711],{},"This is the simplest path for many production Django apps.",[52,76713,76715],{"id":76714},"_2-blue-green-deployment","2) Blue-green deployment",[16,76717,76718],{},"Use two app environments, for example:",[63,76720,76721,76724],{},[66,76722,76723],{},"blue = current production",[66,76725,76726],{},"green = new release candidate",[16,76728,76729],{},"Nginx routes traffic to one environment at a time. You deploy to the inactive one, verify it, then switch traffic.",[16,76731,76732],{},"Why it works well:",[63,76734,76735,76738,76741],{},[66,76736,76737],{},"very fast rollback",[66,76739,76740],{},"safer testing before cutover",[66,76742,76743],{},"cleaner separation between versions",[16,76745,76746],{},"Tradeoff: you need enough resources to run both environments at once.",[52,76748,76750],{"id":76749},"_3-rolling-replacement-with-containers","3) Rolling replacement with containers",[16,76752,76753],{},"Use this when you already deploy with Docker or another container-based process and can run more than one web instance.",[16,76755,76756],{},"Pattern:",[63,76758,76759,76762,76765,76768,76771],{},[66,76760,76761],{},"run multiple web containers",[66,76763,76764],{},"start a new container version",[66,76766,76767],{},"wait for health checks",[66,76769,76770],{},"remove an old container",[66,76772,76773],{},"repeat until all are updated",[16,76775,76776],{},"This is a common way to reduce or avoid downtime in container setups when you run multiple app instances behind a health-aware reverse proxy or orchestrator.",[11,76778,76780],{"id":76779},"prepare-django-for-zero-downtime-releases","Prepare Django for zero-downtime releases",[52,76782,76784],{"id":76783},"_2-add-a-health-check-endpoint","2) Add a health check endpoint",[16,76786,76787],{},"Keep it lightweight. It should confirm the app is ready to serve requests.",[106,76789,76791],{"className":2369,"code":76790,"language":1114,"meta":111,"style":111},"# urls.py\nfrom django.http import JsonResponse\nfrom django.urls import path\n\ndef health(request):\n    return JsonResponse({\"status\": \"ok\"})\n\nurlpatterns = [\n    path(\"health\u002F\", health),\n]\n",[20,76792,76793,76797,76807,76817,76821,76829,76843,76847,76855,76863],{"__ignoreMap":111},[115,76794,76795],{"class":117,"line":118},[115,76796,18587],{"class":3861},[115,76798,76799,76801,76803,76805],{"class":117,"line":136},[115,76800,5621],{"class":121},[115,76802,17240],{"class":125},[115,76804,5613],{"class":121},[115,76806,18598],{"class":125},[115,76808,76809,76811,76813,76815],{"class":117,"line":149},[115,76810,5621],{"class":121},[115,76812,17252],{"class":125},[115,76814,5613],{"class":121},[115,76816,17257],{"class":125},[115,76818,76819],{"class":117,"line":162},[115,76820,310],{"emptyLinePlaceholder":309},[115,76822,76823,76825,76827],{"class":117,"line":175},[115,76824,8808],{"class":121},[115,76826,18620],{"class":262},[115,76828,17271],{"class":125},[115,76830,76831,76833,76835,76837,76839,76841],{"class":117,"line":350},[115,76832,3822],{"class":121},[115,76834,18629],{"class":125},[115,76836,18632],{"class":132},[115,76838,2513],{"class":125},[115,76840,17281],{"class":132},[115,76842,18639],{"class":125},[115,76844,76845],{"class":117,"line":365},[115,76846,310],{"emptyLinePlaceholder":309},[115,76848,76849,76851,76853],{"class":117,"line":380},[115,76850,17302],{"class":125},[115,76852,129],{"class":121},[115,76854,3540],{"class":125},[115,76856,76857,76859,76861],{"class":117,"line":487},[115,76858,17311],{"class":125},[115,76860,18658],{"class":132},[115,76862,18661],{"class":125},[115,76864,76865],{"class":117,"line":2095},[115,76866,2552],{"class":125},[16,76868,76869],{},"If you add a database check, keep it cheap and intentional. Do not perform expensive queries or external API calls here.",[16,76871,8572],{},[106,76873,76875],{"className":108,"code":76874,"language":110,"meta":111,"style":111},"curl -fsS http:\u002F\u002F127.0.0.1:8001\u002Fhealth\u002F\n",[20,76876,76877],{"__ignoreMap":111},[115,76878,76879,76881,76884],{"class":117,"line":118},[115,76880,2764],{"class":262},[115,76882,76883],{"class":202}," -fsS",[115,76885,76886],{"class":132}," http:\u002F\u002F127.0.0.1:8001\u002Fhealth\u002F\n",[16,76888,76889,76890,76893],{},"Expected result: HTTP 200 with ",[20,76891,76892],{},"{\"status\":\"ok\"}"," or similar.",[52,76895,76897],{"id":76896},"_3-set-django-production-and-proxy-settings-correctly","3) Set Django production and proxy settings correctly",[16,76899,76900],{},"If Django is behind Nginx or Caddy and you terminate HTTPS at the proxy, make sure Django trusts the forwarded scheme and is locked down for production.",[106,76902,76904],{"className":2369,"code":76903,"language":1114,"meta":111,"style":111},"# settings.py\nDEBUG = False\nALLOWED_HOSTS = [\"example.com\"]\nSECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")\n\n# Add this if your deployment needs trusted HTTPS origins for CSRF-protected requests\nCSRF_TRUSTED_ORIGINS = [\"https:\u002F\u002Fexample.com\"]\n",[20,76905,76906,76910,76918,76930,76946,76950,76955],{"__ignoreMap":111},[115,76907,76908],{"class":117,"line":118},[115,76909,42173],{"class":3861},[115,76911,76912,76914,76916],{"class":117,"line":136},[115,76913,7350],{"class":202},[115,76915,2380],{"class":121},[115,76917,7355],{"class":202},[115,76919,76920,76922,76924,76926,76928],{"class":117,"line":149},[115,76921,2719],{"class":202},[115,76923,2380],{"class":121},[115,76925,7493],{"class":125},[115,76927,7496],{"class":132},[115,76929,2552],{"class":125},[115,76931,76932,76934,76936,76938,76940,76942,76944],{"class":117,"line":162},[115,76933,2377],{"class":202},[115,76935,2380],{"class":121},[115,76937,2383],{"class":125},[115,76939,2386],{"class":132},[115,76941,1153],{"class":125},[115,76943,2391],{"class":132},[115,76945,2394],{"class":125},[115,76947,76948],{"class":117,"line":175},[115,76949,310],{"emptyLinePlaceholder":309},[115,76951,76952],{"class":117,"line":350},[115,76953,76954],{"class":3861},"# Add this if your deployment needs trusted HTTPS origins for CSRF-protected requests\n",[115,76956,76957,76959,76961,76963,76965],{"class":117,"line":365},[115,76958,2725],{"class":202},[115,76960,2380],{"class":121},[115,76962,7493],{"class":125},[115,76964,15110],{"class":132},[115,76966,2552],{"class":125},[16,76968,76969,76970,76972],{},"Also keep ",[20,76971,2713],{}," in environment variables or a protected environment file, not inside the release directory.",[16,76974,76975],{},"Why this matters:",[63,76977,76978,76983,76988],{},[66,76979,76980,76982],{},[20,76981,2719],{}," prevents host header issues",[66,76984,76985,76987],{},[20,76986,2377],{}," lets Django detect HTTPS correctly behind the proxy",[66,76989,76990],{},"secure redirects, secure cookies, and CSRF behavior depend on correct HTTPS detection",[52,76992,76994],{"id":76993},"_4-make-database-migrations-backward-compatible","4) Make database migrations backward-compatible",[16,76996,6624,76997,77000],{},[1226,76998,76999],{},"expand-contract"," approach:",[63,77002,77003,77006,77009],{},[66,77004,77005],{},"first release: add new columns, tables, or indexes while keeping the old schema usable",[66,77007,77008],{},"deploy code that can read and write both forms if needed",[66,77010,77011],{},"later release: remove old fields only after all app and worker processes are updated",[16,77013,77014],{},"Avoid in a single zero-downtime release:",[63,77016,77017,77020,77023],{},[66,77018,77019],{},"renaming columns without compatibility handling",[66,77021,77022],{},"dropping columns still used by old workers",[66,77024,77025],{},"changing nullable to non-null without a safe backfill plan",[16,77027,77028],{},"Only run migrations before cutover if they are backward-compatible with both the old and new application versions.",[52,77030,77032],{"id":77031},"_5-handle-static-files-safely","5) Handle static files safely",[16,77034,77035],{},"Use versioned static file names through Django’s manifest storage.",[106,77037,77039],{"className":2369,"code":77038,"language":1114,"meta":111,"style":111},"# settings.py\nSTORAGES = {\n    \"staticfiles\": {\n        \"BACKEND\": \"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\",\n    },\n}\n",[20,77040,77041,77045,77053,77059,77070,77074],{"__ignoreMap":111},[115,77042,77043],{"class":117,"line":118},[115,77044,42173],{"class":3861},[115,77046,77047,77049,77051],{"class":117,"line":136},[115,77048,16659],{"class":202},[115,77050,2380],{"class":121},[115,77052,2166],{"class":125},[115,77054,77055,77057],{"class":117,"line":149},[115,77056,16669],{"class":132},[115,77058,3374],{"class":125},[115,77060,77061,77063,77065,77068],{"class":117,"line":162},[115,77062,16677],{"class":132},[115,77064,2513],{"class":125},[115,77066,77067],{"class":132},"\"django.contrib.staticfiles.storage.ManifestStaticFilesStorage\"",[115,77069,3354],{"class":125},[115,77071,77072],{"class":117,"line":175},[115,77073,3403],{"class":125},[115,77075,77076],{"class":117,"line":350},[115,77077,2323],{"class":125},[16,77079,77080],{},"Then collect static files before switching traffic:",[106,77082,77084],{"className":108,"code":77083,"language":110,"meta":111,"style":111},"source \u002Fvar\u002Fwww\u002Fapp\u002Fvenv\u002Fbin\u002Factivate\npython manage.py collectstatic --noinput\n",[20,77085,77086,77093],{"__ignoreMap":111},[115,77087,77088,77090],{"class":117,"line":118},[115,77089,5311],{"class":202},[115,77091,77092],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\u002Fvenv\u002Fbin\u002Factivate\n",[115,77094,77095,77097,77099,77101],{"class":117,"line":136},[115,77096,1114],{"class":262},[115,77098,1117],{"class":132},[115,77100,1838],{"class":132},[115,77102,1841],{"class":202},[16,77104,77105],{},"This avoids old pages referencing assets that disappear after deploy.",[52,77107,77109],{"id":77108},"_6-keep-environment-variables-consistent","6) Keep environment variables consistent",[16,77111,77112],{},"Old and new versions must be able to start with the same production environment unless you are intentionally doing a staged config rollout.",[16,77114,77115],{},"Common rules:",[63,77117,77118,77121,77124],{},[66,77119,77120],{},"do not remove required env vars in the same release",[66,77122,77123],{},"avoid changing secret names and app expectations together",[66,77125,77126],{},"keep your environment file stable across adjacent releases",[11,77128,77130],{"id":77129},"implement-zero-downtime-deployment-with-gunicorn-and-nginx","Implement zero-downtime deployment with Gunicorn and Nginx",[52,77132,77134],{"id":77133},"_7-use-release-directories","7) Use release directories",[16,77136,14802],{},[63,77138,77139,77144,77149],{},[66,77140,77141],{},[20,77142,77143],{},"\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\u002F",[66,77145,77146],{},[20,77147,77148],{},"\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260420-103000\u002F",[66,77150,77151],{},[20,77152,77153],{},"\u002Fvar\u002Fwww\u002Fapp\u002Fcurrent -> \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260420-103000",[16,77155,77156],{},"Create a release:",[106,77158,77160],{"className":108,"code":77159,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\nsudo chown -R deploy:www-data \u002Fvar\u002Fwww\u002Fapp\n",[20,77161,77162,77173],{"__ignoreMap":111},[115,77163,77164,77166,77168,77170],{"class":117,"line":118},[115,77165,2001],{"class":262},[115,77167,6721],{"class":132},[115,77169,1001],{"class":202},[115,77171,77172],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\n",[115,77174,77175,77177,77179,77181,77183],{"class":117,"line":136},[115,77176,2001],{"class":262},[115,77178,6733],{"class":132},[115,77180,6736],{"class":202},[115,77182,13058],{"class":132},[115,77184,77185],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\n",[16,77187,77188],{},"Unpack or sync code into the new release directory, then install dependencies:",[106,77190,77192],{"className":108,"code":77191,"language":110,"meta":111,"style":111},"cd \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\npython3 -m venv venv\nsource venv\u002Fbin\u002Factivate\npip install --upgrade pip\npip install -r requirements.txt\n",[20,77193,77194,77200,77210,77216,77226],{"__ignoreMap":111},[115,77195,77196,77198],{"class":117,"line":118},[115,77197,5303],{"class":202},[115,77199,77172],{"class":132},[115,77201,77202,77204,77206,77208],{"class":117,"line":136},[115,77203,12281],{"class":262},[115,77205,12284],{"class":202},[115,77207,12287],{"class":132},[115,77209,23542],{"class":132},[115,77211,77212,77214],{"class":117,"line":149},[115,77213,5311],{"class":202},[115,77215,23549],{"class":132},[115,77217,77218,77220,77222,77224],{"class":117,"line":162},[115,77219,8618],{"class":262},[115,77221,6600],{"class":132},[115,77223,12338],{"class":202},[115,77225,12341],{"class":132},[115,77227,77228,77230,77232,77234],{"class":117,"line":175},[115,77229,8618],{"class":262},[115,77231,6600],{"class":132},[115,77233,12350],{"class":202},[115,77235,12353],{"class":132},[16,77237,77238],{},"Run static collection and only backward-compatible migrations before traffic cutover:",[106,77240,77242],{"className":108,"code":77241,"language":110,"meta":111,"style":111},"source \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\u002Fvenv\u002Fbin\u002Factivate\nexport DJANGO_SETTINGS_MODULE=config.settings.production\npython manage.py collectstatic --noinput\npython manage.py migrate --noinput\n",[20,77243,77244,77251,77263,77273],{"__ignoreMap":111},[115,77245,77246,77248],{"class":117,"line":118},[115,77247,5311],{"class":202},[115,77249,77250],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\u002Fvenv\u002Fbin\u002Factivate\n",[115,77252,77253,77255,77258,77260],{"class":117,"line":136},[115,77254,122],{"class":121},[115,77256,77257],{"class":125}," DJANGO_SETTINGS_MODULE",[115,77259,129],{"class":121},[115,77261,77262],{"class":125},"config.settings.production\n",[115,77264,77265,77267,77269,77271],{"class":117,"line":149},[115,77266,1114],{"class":262},[115,77268,1117],{"class":132},[115,77270,1838],{"class":132},[115,77272,1841],{"class":202},[115,77274,77275,77277,77279,77281],{"class":117,"line":162},[115,77276,1114],{"class":262},[115,77278,1117],{"class":132},[115,77280,1826],{"class":132},[115,77282,1841],{"class":202},[16,77284,77285],{},"Rollback note: do not move traffic yet. If these steps fail, production traffic is still on the old release.",[52,77287,77289],{"id":77288},"_8-start-the-new-gunicorn-instance-before-cutover","8) Start the new Gunicorn instance before cutover",[16,77291,77292],{},"A clean single-host method is to run the new release on a new local port, then switch Nginx.",[16,77294,77295],{},"Start a second Gunicorn process:",[106,77297,77299],{"className":108,"code":77298,"language":110,"meta":111,"style":111},"cd \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\nsource venv\u002Fbin\u002Factivate\n\ngunicorn config.wsgi:application \\\n  --bind 127.0.0.1:8001 \\\n  --workers 4 \\\n  --timeout 30 \\\n  --graceful-timeout 30 \\\n  --access-logfile - \\\n  --error-logfile -\n",[20,77300,77301,77307,77313,77317,77325,77334,77343,77353,77362,77370],{"__ignoreMap":111},[115,77302,77303,77305],{"class":117,"line":118},[115,77304,5303],{"class":202},[115,77306,77172],{"class":132},[115,77308,77309,77311],{"class":117,"line":136},[115,77310,5311],{"class":202},[115,77312,23549],{"class":132},[115,77314,77315],{"class":117,"line":149},[115,77316,310],{"emptyLinePlaceholder":309},[115,77318,77319,77321,77323],{"class":117,"line":162},[115,77320,14954],{"class":262},[115,77322,32822],{"class":132},[115,77324,317],{"class":202},[115,77326,77327,77329,77332],{"class":117,"line":175},[115,77328,36533],{"class":202},[115,77330,77331],{"class":132}," 127.0.0.1:8001",[115,77333,317],{"class":202},[115,77335,77336,77338,77341],{"class":117,"line":350},[115,77337,36542],{"class":202},[115,77339,77340],{"class":202}," 4",[115,77342,317],{"class":202},[115,77344,77345,77348,77351],{"class":117,"line":365},[115,77346,77347],{"class":202},"  --timeout",[115,77349,77350],{"class":202}," 30",[115,77352,317],{"class":202},[115,77354,77355,77358,77360],{"class":117,"line":380},[115,77356,77357],{"class":202},"  --graceful-timeout",[115,77359,77350],{"class":202},[115,77361,317],{"class":202},[115,77363,77364,77366,77368],{"class":117,"line":487},[115,77365,36552],{"class":202},[115,77367,4009],{"class":132},[115,77369,317],{"class":202},[115,77371,77372,77374],{"class":117,"line":2095},[115,77373,36561],{"class":202},[115,77375,77376],{"class":132}," -\n",[16,77378,77379],{},"In production, manage this with systemd rather than a shell session. Example unit:",[106,77381,77383],{"className":2026,"code":77382,"language":2028,"meta":111,"style":111},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-app-green.service\n[Unit]\nDescription=Gunicorn Django app green\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\nEnvironmentFile=\u002Fetc\u002Fapp\u002Fapp.env\nExecStart=\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application \\\n    --bind 127.0.0.1:8001 \\\n    --workers 4 \\\n    --timeout 30 \\\n    --graceful-timeout 30\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n",[20,77384,77385,77390,77394,77401,77407,77411,77415,77421,77427,77434,77441,77448,77453,77458,77462,77467,77473,77477,77481],{"__ignoreMap":111},[115,77386,77387],{"class":117,"line":118},[115,77388,77389],{"class":3861},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn-app-green.service\n",[115,77391,77392],{"class":117,"line":136},[115,77393,2035],{"class":262},[115,77395,77396,77398],{"class":117,"line":149},[115,77397,2040],{"class":121},[115,77399,77400],{"class":125},"=Gunicorn Django app green\n",[115,77402,77403,77405],{"class":117,"line":162},[115,77404,2048],{"class":121},[115,77406,2051],{"class":125},[115,77408,77409],{"class":117,"line":175},[115,77410,310],{"emptyLinePlaceholder":309},[115,77412,77413],{"class":117,"line":350},[115,77414,2060],{"class":262},[115,77416,77417,77419],{"class":117,"line":365},[115,77418,2065],{"class":121},[115,77420,2076],{"class":125},[115,77422,77423,77425],{"class":117,"line":380},[115,77424,2073],{"class":121},[115,77426,2076],{"class":125},[115,77428,77429,77431],{"class":117,"line":487},[115,77430,2081],{"class":121},[115,77432,77433],{"class":125},"=\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\n",[115,77435,77436,77438],{"class":117,"line":2095},[115,77437,2089],{"class":121},[115,77439,77440],{"class":125},"=\u002Fetc\u002Fapp\u002Fapp.env\n",[115,77442,77443,77445],{"class":117,"line":2104},[115,77444,2107],{"class":121},[115,77446,77447],{"class":125},"=\u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application \\\n",[115,77449,77450],{"class":117,"line":2113},[115,77451,77452],{"class":125},"    --bind 127.0.0.1:8001 \\\n",[115,77454,77455],{"class":117,"line":2122},[115,77456,77457],{"class":125},"    --workers 4 \\\n",[115,77459,77460],{"class":117,"line":2131},[115,77461,52384],{"class":125},[115,77463,77464],{"class":117,"line":2136},[115,77465,77466],{"class":125},"    --graceful-timeout 30\n",[115,77468,77469,77471],{"class":117,"line":2142},[115,77470,2116],{"class":121},[115,77472,2119],{"class":125},[115,77474,77475],{"class":117,"line":2273},[115,77476,310],{"emptyLinePlaceholder":309},[115,77478,77479],{"class":117,"line":2282},[115,77480,2139],{"class":262},[115,77482,77483,77485],{"class":117,"line":2291},[115,77484,2145],{"class":121},[115,77486,2148],{"class":125},[16,77488,77489],{},"Start and verify:",[106,77491,77493],{"className":108,"code":77492,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl start gunicorn-app-green\ncurl -fsS http:\u002F\u002F127.0.0.1:8001\u002Fhealth\u002F\n",[20,77494,77495,77503,77514],{"__ignoreMap":111},[115,77496,77497,77499,77501],{"class":117,"line":118},[115,77498,2001],{"class":262},[115,77500,3480],{"class":132},[115,77502,4984],{"class":132},[115,77504,77505,77507,77509,77511],{"class":117,"line":136},[115,77506,2001],{"class":262},[115,77508,3480],{"class":132},[115,77510,15489],{"class":132},[115,77512,77513],{"class":132}," gunicorn-app-green\n",[115,77515,77516,77518,77520],{"class":117,"line":149},[115,77517,2764],{"class":262},[115,77519,76883],{"class":202},[115,77521,76886],{"class":132},[52,77523,77525],{"id":77524},"_9-reload-nginx-without-dropping-connections","9) Reload Nginx without dropping connections",[16,77527,77528],{},"Nginx reloads configuration gracefully. Test before reloading.",[16,77530,77531],{},"Example upstream switch:",[106,77533,77535],{"className":2154,"code":77534,"language":2156,"meta":111,"style":111},"upstream django_app {\n    server 127.0.0.1:8001;\n    keepalive 32;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fdjango_app;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_read_timeout 60s;\n    }\n}\n",[20,77536,77537,77545,77552,77562,77566,77570,77576,77584,77590,77594,77602,77608,77614,77620,77626,77632,77640,77644],{"__ignoreMap":111},[115,77538,77539,77541,77543],{"class":117,"line":118},[115,77540,52563],{"class":121},[115,77542,52566],{"class":262},[115,77544,2220],{"class":125},[115,77546,77547,77549],{"class":117,"line":136},[115,77548,52573],{"class":121},[115,77550,77551],{"class":125}," 127.0.0.1:8001;\n",[115,77553,77554,77557,77560],{"class":117,"line":149},[115,77555,77556],{"class":121},"    keepalive ",[115,77558,77559],{"class":202},"32",[115,77561,3811],{"class":125},[115,77563,77564],{"class":117,"line":162},[115,77565,2323],{"class":125},[115,77567,77568],{"class":117,"line":175},[115,77569,310],{"emptyLinePlaceholder":309},[115,77571,77572,77574],{"class":117,"line":350},[115,77573,2163],{"class":121},[115,77575,2166],{"class":125},[115,77577,77578,77580,77582],{"class":117,"line":365},[115,77579,2171],{"class":121},[115,77581,2174],{"class":202},[115,77583,2177],{"class":125},[115,77585,77586,77588],{"class":117,"line":380},[115,77587,2182],{"class":121},[115,77589,2185],{"class":125},[115,77591,77592],{"class":117,"line":487},[115,77593,310],{"emptyLinePlaceholder":309},[115,77595,77596,77598,77600],{"class":117,"line":2095},[115,77597,2214],{"class":121},[115,77599,2268],{"class":262},[115,77601,2220],{"class":125},[115,77603,77604,77606],{"class":117,"line":2104},[115,77605,2276],{"class":121},[115,77607,52685],{"class":125},[115,77609,77610,77612],{"class":117,"line":2113},[115,77611,2285],{"class":121},[115,77613,2288],{"class":125},[115,77615,77616,77618],{"class":117,"line":2122},[115,77617,2285],{"class":121},[115,77619,3767],{"class":125},[115,77621,77622,77624],{"class":117,"line":2131},[115,77623,2285],{"class":121},[115,77625,2312],{"class":125},[115,77627,77628,77630],{"class":117,"line":2136},[115,77629,2285],{"class":121},[115,77631,2304],{"class":125},[115,77633,77634,77636,77638],{"class":117,"line":2142},[115,77635,12916],{"class":121},[115,77637,52731],{"class":202},[115,77639,3811],{"class":125},[115,77641,77642],{"class":117,"line":2273},[115,77643,2233],{"class":125},[115,77645,77646],{"class":117,"line":2282},[115,77647,2323],{"class":125},[16,77649,42761],{},[106,77651,77652],{"className":108,"code":7586,"language":110,"meta":111,"style":111},[20,77653,77654,77662],{"__ignoreMap":111},[115,77655,77656,77658,77660],{"class":117,"line":118},[115,77657,2001],{"class":262},[115,77659,3906],{"class":132},[115,77661,4282],{"class":202},[115,77663,77664,77666,77668,77670],{"class":117,"line":136},[115,77665,2001],{"class":262},[115,77667,3480],{"class":132},[115,77669,3919],{"class":132},[115,77671,1996],{"class":132},[16,77673,8572],{},[106,77675,77676],{"className":108,"code":13415,"language":110,"meta":111,"style":111},[20,77677,77678],{"__ignoreMap":111},[115,77679,77680,77682,77684],{"class":117,"line":118},[115,77681,2764],{"class":262},[115,77683,2767],{"class":202},[115,77685,13426],{"class":132},[16,77687,77688],{},"Then repoint the symlink atomically for operator clarity only. In this port-switch pattern, the running Gunicorn process is already using the new release directory directly.",[106,77690,77692],{"className":108,"code":77691,"language":110,"meta":111,"style":111},"ln -sfn \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000 \u002Fvar\u002Fwww\u002Fapp\u002Fcurrent\n",[20,77693,77694],{"__ignoreMap":111},[115,77695,77696,77698,77700,77703],{"class":117,"line":118},[115,77697,14854],{"class":262},[115,77699,14857],{"class":202},[115,77701,77702],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\u002Freleases\u002F20260424-120000",[115,77704,77705],{"class":132}," \u002Fvar\u002Fwww\u002Fapp\u002Fcurrent\n",[16,77707,77708],{},"After traffic is stable, stop the old Gunicorn service.",[16,77710,52937],{},[63,77712,77713,77716,77718],{},[66,77714,77715],{},"switch Nginx upstream back to the old port or old service",[66,77717,46127],{},[66,77719,77720,77721,77723],{},"repoint ",[20,77722,13654],{}," to the previous release if needed",[11,77725,77727],{"id":77726},"implement-zero-downtime-deployment-with-docker","Implement zero-downtime deployment with Docker",[52,77729,77731],{"id":77730},"_10-use-a-health-check-that-exists-in-your-image","10) Use a health check that exists in your image",[16,77733,43602,77734,77737],{},[1226,77735,77736],{},"docker zero downtime Django deployment",", you need more than one app instance and a reverse proxy that can send traffic only to healthy containers.",[16,77739,77740,77741,12360],{},"Example Compose service with a health check that uses Python instead of assuming ",[20,77742,2764],{},[106,77744,77746],{"className":2485,"code":77745,"language":2487,"meta":111,"style":111},"services:\n  web:\n    image: registry.example.com\u002Fmyapp:2026-04-24\n    env_file:\n      - \u002Fetc\u002Fapp\u002Fapp.env\n    healthcheck:\n      test: [\"CMD\", \"python\", \"-c\", \"import urllib.request; urllib.request.urlopen('http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F')\"]\n      interval: 10s\n      timeout: 3s\n      retries: 5\n",[20,77747,77748,77754,77760,77769,77775,77782,77788,77814,77824,77834],{"__ignoreMap":111},[115,77749,77750,77752],{"class":117,"line":118},[115,77751,2495],{"class":2494},[115,77753,2498],{"class":125},[115,77755,77756,77758],{"class":117,"line":136},[115,77757,2503],{"class":2494},[115,77759,2498],{"class":125},[115,77761,77762,77764,77766],{"class":117,"line":149},[115,77763,2510],{"class":2494},[115,77765,2513],{"class":125},[115,77767,77768],{"class":132},"registry.example.com\u002Fmyapp:2026-04-24\n",[115,77770,77771,77773],{"class":117,"line":162},[115,77772,2521],{"class":2494},[115,77774,2498],{"class":125},[115,77776,77777,77779],{"class":117,"line":175},[115,77778,5976],{"class":125},[115,77780,77781],{"class":132},"\u002Fetc\u002Fapp\u002Fapp.env\n",[115,77783,77784,77786],{"class":117,"line":350},[115,77785,2531],{"class":2494},[115,77787,2498],{"class":125},[115,77789,77790,77792,77794,77797,77799,77802,77804,77807,77809,77812],{"class":117,"line":365},[115,77791,2538],{"class":2494},[115,77793,2541],{"class":125},[115,77795,77796],{"class":132},"\"CMD\"",[115,77798,1153],{"class":125},[115,77800,77801],{"class":132},"\"python\"",[115,77803,1153],{"class":125},[115,77805,77806],{"class":132},"\"-c\"",[115,77808,1153],{"class":125},[115,77810,77811],{"class":132},"\"import urllib.request; urllib.request.urlopen('http:\u002F\u002F127.0.0.1:8000\u002Fhealth\u002F')\"",[115,77813,2552],{"class":125},[115,77815,77816,77819,77821],{"class":117,"line":380},[115,77817,77818],{"class":2494},"      interval",[115,77820,2513],{"class":125},[115,77822,77823],{"class":132},"10s\n",[115,77825,77826,77829,77831],{"class":117,"line":487},[115,77827,77828],{"class":2494},"      timeout",[115,77830,2513],{"class":125},[115,77832,77833],{"class":132},"3s\n",[115,77835,77836,77839,77841],{"class":117,"line":2095},[115,77837,77838],{"class":2494},"      retries",[115,77840,2513],{"class":125},[115,77842,77843],{"class":202},"5\n",[52,77845,77847],{"id":77846},"_11-scale-to-more-than-one-app-instance-first","11) Scale to more than one app instance first",[106,77849,77851],{"className":108,"code":77850,"language":110,"meta":111,"style":111},"docker compose up -d --scale web=2\ndocker compose ps\n",[20,77852,77853,77872],{"__ignoreMap":111},[115,77854,77855,77857,77859,77861,77863,77866,77869],{"class":117,"line":118},[115,77856,3295],{"class":262},[115,77858,3298],{"class":132},[115,77860,3502],{"class":132},[115,77862,1019],{"class":202},[115,77864,77865],{"class":202}," --scale",[115,77867,77868],{"class":132}," web=",[115,77870,77871],{"class":202},"2\n",[115,77873,77874,77876,77878],{"class":117,"line":136},[115,77875,3295],{"class":262},[115,77877,3298],{"class":132},[115,77879,4790],{"class":132},[16,77881,77882],{},"Then verify your reverse proxy can actually route to multiple healthy app instances. Plain Docker Compose does not provide a full rolling deployment controller, so the exact replacement sequence must be handled explicitly by your proxy and container workflow.",[16,77884,77885],{},"In practice, that usually means one of these:",[63,77887,77888,77891],{},[66,77889,77890],{},"Nginx or Caddy in front of multiple app containers on the same Docker network",[66,77892,77893],{},"an orchestrator that supports rolling updates and readiness checks directly",[52,77895,77897],{"id":77896},"_12-replace-containers-gradually","12) Replace containers gradually",[16,77899,77900],{},"Do not restart every app container at once.",[16,77902,77903],{},"Safer pattern:",[1173,77905,77906,77909,77912,77915,77918,77921],{},[66,77907,77908],{},"pull the new image",[66,77910,77911],{},"start one new container",[66,77913,77914],{},"wait for healthy status",[66,77916,77917],{},"confirm the proxy is sending traffic to it",[66,77919,77920],{},"remove one old container",[66,77922,77923],{},"repeat",[16,77925,77926],{},"If your setup cannot target healthy new containers while old ones still serve traffic, plain Compose alone is not enough for a true zero-downtime rollout.",[16,77928,77929],{},"Rollback is easiest when images are tagged per release. Switch back to the prior image tag and repeat the same replacement process.",[11,77931,77933],{"id":77932},"verification-after-the-deploy","Verification after the deploy",[52,77935,77937],{"id":77936},"_13-check-health-and-smoke-tests","13) Check health and smoke tests",[16,77939,77940],{},"Run at least:",[106,77942,77944],{"className":108,"code":77943,"language":110,"meta":111,"style":111},"curl -fsS https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,77945,77946],{"__ignoreMap":111},[115,77947,77948,77950,77952],{"class":117,"line":118},[115,77949,2764],{"class":262},[115,77951,76883],{"class":202},[115,77953,13426],{"class":132},[16,77955,77956],{},"Then verify one real path:",[63,77958,77959,77962,77965],{},[66,77960,77961],{},"homepage",[66,77963,77964],{},"authenticated admin login",[66,77966,77967],{},"a key API endpoint",[52,77969,77971],{"id":77970},"_14-check-migrations-static-files-and-workers","14) Check migrations, static files, and workers",[16,77973,22752],{},[63,77975,77976,77979,77982],{},[66,77977,77978],{},"new code can read the current schema",[66,77980,77981],{},"static CSS and JS files load correctly",[66,77983,77984],{},"Celery or background workers are compatible with the deployed web release",[16,77986,77987],{},"If worker code depends on schema changes, update in a compatible sequence. Do not leave old workers running against incompatible tasks or models.",[52,77989,77991],{"id":77990},"_15-check-logs-and-error-rate-immediately","15) Check logs and error rate immediately",[16,77993,77994],{},"Review:",[106,77996,77998],{"className":108,"code":77997,"language":110,"meta":111,"style":111},"sudo journalctl -u gunicorn-app-green -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[20,77999,78000,78017],{"__ignoreMap":111},[115,78001,78002,78004,78006,78008,78011,78013,78015],{"class":117,"line":118},[115,78003,2001],{"class":262},[115,78005,5030],{"class":132},[115,78007,2788],{"class":202},[115,78009,78010],{"class":132}," gunicorn-app-green",[115,78012,2794],{"class":202},[115,78014,2797],{"class":202},[115,78016,2800],{"class":202},[115,78018,78019,78021,78023,78025,78027],{"class":117,"line":136},[115,78020,2001],{"class":262},[115,78022,13188],{"class":132},[115,78024,2794],{"class":202},[115,78026,2797],{"class":202},[115,78028,13195],{"class":132},[16,78030,78031],{},"Also check Sentry, APM, or metrics if available. A release that starts successfully can still fail under real traffic.",[11,78033,1321],{"id":1320},[16,78035,78036],{},"This works because traffic is only moved after the new Django version is already running and verified. Nginx reloads gracefully, and the release directory pattern makes rollback predictable. The same principle applies in container environments: keep the old version serving until the new version is confirmed healthy.",[16,78038,78039],{},"Choose by environment:",[63,78041,78042,78048,78054],{},[66,78043,78044,78047],{},[1226,78045,78046],{},"Gunicorn + Nginx",": best for single-server manual deployments",[66,78049,78050,78053],{},[1226,78051,78052],{},"blue-green",": best when rollback speed matters most",[66,78055,78056,78059],{},[1226,78057,78058],{},"multi-instance containers",": best when you already run a proxy or orchestrator that supports health-based cutover",[16,78061,78062],{},"The main failure mode is still schema compatibility. Zero-downtime process restarts do not protect you from unsafe migrations.",[11,78064,1337],{"id":1336},[63,78066,78067,78076,78082,78087,78098,78104,78110],{},[66,78068,78069,78071,78072,78075],{},[1226,78070,30563],{}," set ",[20,78073,78074],{},"--graceful-timeout"," high enough for normal in-flight requests to finish.",[66,78077,78078,78081],{},[1226,78079,78080],{},"WebSockets\u002FASGI:"," if you run Django with ASGI and Uvicorn, use the same readiness and phased cutover pattern, but validate connection draining carefully.",[66,78083,78084,78086],{},[1226,78085,27007],{}," user uploads should be on shared persistent storage, not inside a release directory.",[66,78088,78089,78091,78092,78094,78095,78097],{},[1226,78090,46356],{}," terminate TLS at Nginx or Caddy, pass ",[20,78093,3203],{},", and set ",[20,78096,2377],{}," in Django.",[66,78099,78100,78103],{},[1226,78101,78102],{},"Static file pathing:"," if Nginx serves static files from disk, make sure your static path is not accidentally tied only to an inactive release.",[66,78105,78106,78109],{},[1226,78107,78108],{},"Single instance only:"," if you have exactly one app process and no parallel startup path, true zero-downtime deploys are limited.",[66,78111,78112,78115],{},[1226,78113,78114],{},"Schema rollback:"," database rollback is often riskier than code rollback. Prefer forward fixes unless you have tested down migrations.",[52,78117,78119],{"id":78118},"when-manual-zero-downtime-deployment-becomes-repetitive","When manual zero-downtime deployment becomes repetitive",[16,78121,78122],{},"Once you are repeatedly creating release directories, polling health checks, switching upstreams, validating Nginx config, and rolling back failed smoke tests, the process should be scripted. Those steps are good candidates for reusable deploy scripts, systemd unit templates, reverse-proxy templates, and CI jobs. Automating them reduces operator error more than it changes the deployment design itself.",[11,78124,1386],{"id":1385},[16,78126,78127,78128,211],{},"For the concepts behind readiness, graceful restarts, and migration safety, see ",[1226,78129,78130],{},[1395,78131,78132],{"href":37876},"What Zero-Downtime Deployment Means for Django in Production",[16,78134,78135,78136,211],{},"If you need the underlying server setup first, follow ",[1226,78137,78138],{},[1395,78139,2986],{"href":2985},[16,78141,78142,78143,211],{},"If you run ASGI instead of WSGI, see ",[1226,78144,78145],{},[1395,78146,8039],{"href":8038},[16,78148,78149,78150,211],{},"For a production hardening review before cutover, use ",[1226,78151,78152],{},[1395,78153,3000],{"href":2999},[16,78155,78156,78157,211],{},"For failure recovery, use ",[1226,78158,78159],{},[1395,78160,17929],{"href":1415},[11,78162,1420],{"id":1419},[52,78164,78166],{"id":78165},"can-django-deployments-really-be-zero-downtime-if-i-have-database-migrations","Can Django deployments really be zero downtime if I have database migrations?",[16,78168,78169],{},"Yes, but only if migrations are backward-compatible during the transition. Adding columns is usually safe. Dropping or renaming schema used by old code is not safe in the same release.",[52,78171,78173],{"id":78172},"what-is-the-safest-zero-downtime-strategy-for-a-single-server-django-app","What is the safest zero-downtime strategy for a single-server Django app?",[16,78175,78176],{},"Usually Gunicorn behind Nginx with parallel startup, health checks, and a controlled Nginx switch. If the host has enough resources, a blue-green layout on the same server gives easier rollback.",[52,78178,78180],{"id":78179},"how-do-i-deploy-gunicorn-without-dropping-active-requests","How do I deploy Gunicorn without dropping active requests?",[16,78182,78183],{},"Start the new app instance first, wait for readiness, then switch traffic. Keep the old instance running long enough for in-flight requests to finish before stopping it.",[52,78185,78187],{"id":78186},"how-do-i-roll-back-if-the-new-release-passes-startup-but-fails-after-cutover","How do I roll back if the new release passes startup but fails after cutover?",[16,78189,78190],{},"Switch traffic back to the previous app instance or upstream immediately, then investigate. Keep the previous release directory and image tag available so rollback is fast and does not require rebuilding.",[52,78192,78194],{"id":78193},"do-i-need-docker-or-kubernetes-for-zero-downtime-django-deployment","Do I need Docker or Kubernetes for zero-downtime Django deployment?",[16,78196,78197],{},"No. Docker helps when you run multiple app instances behind a suitable proxy or orchestrator, but a Linux host with systemd, Gunicorn, and Nginx can handle zero-downtime Django deployment if you run old and new versions in parallel and keep migrations compatible.",[1485,78199,78200],{},"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 .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 .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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":78202},[78203,78204,78205,78206,78211,78218,78223,78228,78233,78234,78237,78238],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":76672,"depth":136,"text":76673,"children":78207},[78208,78209,78210],{"id":76676,"depth":149,"text":76677},{"id":76714,"depth":149,"text":76715},{"id":76749,"depth":149,"text":76750},{"id":76779,"depth":136,"text":76780,"children":78212},[78213,78214,78215,78216,78217],{"id":76783,"depth":149,"text":76784},{"id":76896,"depth":149,"text":76897},{"id":76993,"depth":149,"text":76994},{"id":77031,"depth":149,"text":77032},{"id":77108,"depth":149,"text":77109},{"id":77129,"depth":136,"text":77130,"children":78219},[78220,78221,78222],{"id":77133,"depth":149,"text":77134},{"id":77288,"depth":149,"text":77289},{"id":77524,"depth":149,"text":77525},{"id":77726,"depth":136,"text":77727,"children":78224},[78225,78226,78227],{"id":77730,"depth":149,"text":77731},{"id":77846,"depth":149,"text":77847},{"id":77896,"depth":149,"text":77897},{"id":77932,"depth":136,"text":77933,"children":78229},[78230,78231,78232],{"id":77936,"depth":149,"text":77937},{"id":77970,"depth":149,"text":77971},{"id":77990,"depth":149,"text":77991},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":78235},[78236],{"id":78118,"depth":149,"text":78119},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":78239},[78240,78241,78242,78243,78244],{"id":78165,"depth":149,"text":78166},{"id":78172,"depth":149,"text":78173},{"id":78179,"depth":149,"text":78180},{"id":78186,"depth":149,"text":78187},{"id":78193,"depth":149,"text":78194},"A standard Django deploy often causes a brief outage because the old app process stops before the new one is fully ready.",{},"\u002Fzero-downtime-django-deployment",[1415,37877,1552],{"title":76592,"description":78245},[1557,14954,2156,1277],"zero-downtime-django-deployment",[1557,14954,2156,1277],"o8zun4xvxXjiwfkoz1OptpsgvruGjwJxuxdMVDFYcoQ",{"id":78255,"title":78256,"body":78257,"category":3088,"description":78263,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":79865,"navigation":309,"path":79866,"priority":79867,"related":79868,"role":1553,"section":3098,"seo":79869,"stack":79870,"stem":79872,"tags":79873,"type":1561,"__hash__":79874},"articles\u002Fstore-django-media-files-on-s3.md","How to Store Django Media Files on Amazon S3",{"type":8,"value":78258,"toc":79832},[78259,78261,78264,78267,78284,78287,78289,78292,78320,78323,78325,78329,78333,78336,78354,78357,78361,78364,78381,78384,78388,78391,78395,78398,78401,78432,78435,78437,78451,78455,78458,78463,78469,78677,78680,78683,78708,78711,78715,78718,78735,78740,78755,78763,78791,78793,78807,78811,78816,79035,79038,79053,79056,79066,79070,79073,79076,79269,79272,79276,79279,79302,79305,79309,79314,79317,79342,79345,79363,79366,79388,79391,79402,79405,79416,79420,79423,79426,79443,79446,79460,79463,79475,79565,79568,79595,79598,79601,79605,79608,79631,79634,79656,79659,79663,79666,79680,79683,79686,79689,79691,79694,79697,79700,79702,79746,79748,79751,79753,79760,79762,79777,79784,79792,79794,79798,79801,79805,79808,79812,79815,79819,79822,79826,79829],[11,78260,14],{"id":13},[16,78262,78263],{},"Local disk storage for Django media files works in development, but it breaks quickly in production.",[16,78265,78266],{},"If your app stores user uploads on the application server filesystem, you will run into problems such as:",[63,78268,78269,78272,78275,78278,78281],{},[66,78270,78271],{},"uploads disappearing after container replacement",[66,78273,78274],{},"missing files after deploying to a new server",[66,78276,78277],{},"inconsistent media across multiple app instances",[66,78279,78280],{},"operational inconsistencies during rollback because uploaded files are not versioned with your application code",[66,78282,78283],{},"no simple way to share media between web and worker processes",[16,78285,78286],{},"This is especially common with Docker, autoscaling, platform-managed deployments, and multi-server setups. User uploads need persistent storage outside the app container or VM. For many Django deployments, Amazon S3 is a practical production-ready option.",[11,78288,30],{"id":29},[16,78290,78291],{},"To store Django media files on Amazon S3 in production:",[1173,78293,78294,78297,78300,78308,78311,78314,78317],{},[66,78295,78296],{},"Create a dedicated S3 bucket for media.",[66,78298,78299],{},"Keep media storage separate from static files.",[66,78301,75275,78302,3146,78305,211],{},[20,78303,78304],{},"django-storages",[20,78306,78307],{},"boto3",[66,78309,78310],{},"Configure Django to use S3 for media storage.",[66,78312,78313],{},"Give the app least-privilege IAM access to the bucket.",[66,78315,78316],{},"Migrate existing media files before switching production writes.",[66,78318,78319],{},"Verify uploads, reads, and old file paths after deployment.",[16,78321,78322],{},"For the safest default, keep the bucket private and use signed access rather than exposing raw public S3 object URLs.",[11,78324,43],{"id":42},[11,78326,78328],{"id":78327},"_1-decide-what-should-go-to-s3","1) Decide what should go to S3",[52,78330,78332],{"id":78331},"media-files-vs-static-files-in-django","Media files vs static files in Django",[16,78334,78335],{},"In Django:",[63,78337,78338,78344],{},[66,78339,78340,78343],{},[1226,78341,78342],{},"static files"," are build-time assets like CSS, JS, and images shipped with your code",[66,78345,78346,78349,78350,4493,78352],{},[1226,78347,78348],{},"media files"," are user-uploaded files stored through ",[20,78351,48590],{},[20,78353,48587],{},[16,78355,78356],{},"Do not mix them in the same storage path. You can use S3 for both, but configure them separately.",[52,78358,78360],{"id":78359},"why-user-uploads-should-not-depend-on-local-server-disk","Why user uploads should not depend on local server disk",[16,78362,78363],{},"User uploads must survive:",[63,78365,78366,78369,78372,78375,78378],{},[66,78367,78368],{},"redeploys",[66,78370,78371],{},"restarts",[66,78373,78374],{},"scaling to multiple app instances",[66,78376,78377],{},"instance replacement",[66,78379,78380],{},"storage moves between hosts",[16,78382,78383],{},"S3 gives you durable object storage without requiring a shared filesystem.",[52,78385,78387],{"id":78386},"when-s3-is-the-right-choice","When S3 is the right choice",[16,78389,78390],{},"S3 is usually the right choice when you run Django in containers, on multiple servers, or on any platform with ephemeral local storage. Local disk or shared volumes can still be acceptable for small single-server deployments, but they are harder to scale, back up, and recover.",[11,78392,78394],{"id":78393},"_2-create-and-prepare-the-s3-bucket","2) Create and prepare the S3 bucket",[16,78396,78397],{},"Create a bucket in the AWS region closest to your app.",[16,78399,78400],{},"Recommended bucket setup:",[63,78402,78403,78409,78415,78421,78426],{},[66,78404,78405,78406],{},"bucket name dedicated to one environment, such as ",[20,78407,78408],{},"myapp-prod-media",[66,78410,78411,78414],{},[1226,78412,78413],{},"Block Public Access"," enabled by default",[66,78416,78417,78420],{},[1226,78418,78419],{},"Versioning"," enabled",[66,78422,78423,78420],{},[1226,78424,78425],{},"Default encryption",[66,78427,78428,78429],{},"optional prefix such as ",[20,78430,78431],{},"media\u002F",[16,78433,78434],{},"If files are intended to be public, expose them later with an explicit policy decision. Start private unless you are sure public access is correct.",[16,78436,17389],{},[63,78438,78439,78442,78445,78448],{},[66,78440,78441],{},"confirm bucket exists in the intended region",[66,78443,78444],{},"confirm versioning is enabled",[66,78446,78447],{},"confirm encryption is enabled",[66,78449,78450],{},"confirm public access block matches your intended access model",[11,78452,78454],{"id":78453},"_3-create-iam-credentials-with-least-privilege","3) Create IAM credentials with least privilege",[16,78456,78457],{},"Use an IAM role if your app runs on AWS infrastructure that supports instance or task roles. Otherwise use a dedicated IAM user for the Django app.",[16,78459,7471,78460,78462],{},[1226,78461,7474],{}," use root credentials.",[16,78464,78465,78466,78468],{},"Example policy scoped to one bucket and the ",[20,78467,78431],{}," prefix:",[106,78470,78474],{"className":78471,"code":78472,"language":78473,"meta":111,"style":111},"language-json shiki shiki-themes github-light github-dark","{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"ListMediaPrefix\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:ListBucket\"],\n      \"Resource\": \"arn:aws:s3:::myapp-prod-media\",\n      \"Condition\": {\n        \"StringLike\": {\n          \"s3:prefix\": [\"media\u002F*\"]\n        }\n      }\n    },\n    {\n      \"Sid\": \"MediaObjects\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"s3:GetObject\",\n        \"s3:PutObject\",\n        \"s3:DeleteObject\",\n        \"s3:AbortMultipartUpload\"\n      ],\n      \"Resource\": \"arn:aws:s3:::myapp-prod-media\u002Fmedia\u002F*\"\n    }\n  ]\n}\n","json",[20,78475,78476,78480,78492,78500,78505,78517,78529,78541,78553,78560,78567,78579,78584,78589,78593,78597,78608,78618,78624,78631,78638,78645,78650,78655,78664,78668,78673],{"__ignoreMap":111},[115,78477,78478],{"class":117,"line":118},[115,78479,2220],{"class":125},[115,78481,78482,78485,78487,78490],{"class":117,"line":136},[115,78483,78484],{"class":202},"  \"Version\"",[115,78486,2513],{"class":125},[115,78488,78489],{"class":132},"\"2012-10-17\"",[115,78491,3354],{"class":125},[115,78493,78494,78497],{"class":117,"line":149},[115,78495,78496],{"class":202},"  \"Statement\"",[115,78498,78499],{"class":125},": [\n",[115,78501,78502],{"class":117,"line":162},[115,78503,78504],{"class":125},"    {\n",[115,78506,78507,78510,78512,78515],{"class":117,"line":175},[115,78508,78509],{"class":202},"      \"Sid\"",[115,78511,2513],{"class":125},[115,78513,78514],{"class":132},"\"ListMediaPrefix\"",[115,78516,3354],{"class":125},[115,78518,78519,78522,78524,78527],{"class":117,"line":350},[115,78520,78521],{"class":202},"      \"Effect\"",[115,78523,2513],{"class":125},[115,78525,78526],{"class":132},"\"Allow\"",[115,78528,3354],{"class":125},[115,78530,78531,78534,78536,78539],{"class":117,"line":365},[115,78532,78533],{"class":202},"      \"Action\"",[115,78535,2541],{"class":125},[115,78537,78538],{"class":132},"\"s3:ListBucket\"",[115,78540,3430],{"class":125},[115,78542,78543,78546,78548,78551],{"class":117,"line":380},[115,78544,78545],{"class":202},"      \"Resource\"",[115,78547,2513],{"class":125},[115,78549,78550],{"class":132},"\"arn:aws:s3:::myapp-prod-media\"",[115,78552,3354],{"class":125},[115,78554,78555,78558],{"class":117,"line":487},[115,78556,78557],{"class":202},"      \"Condition\"",[115,78559,3374],{"class":125},[115,78561,78562,78565],{"class":117,"line":2095},[115,78563,78564],{"class":202},"        \"StringLike\"",[115,78566,3374],{"class":125},[115,78568,78569,78572,78574,78577],{"class":117,"line":2104},[115,78570,78571],{"class":202},"          \"s3:prefix\"",[115,78573,2541],{"class":125},[115,78575,78576],{"class":132},"\"media\u002F*\"",[115,78578,2552],{"class":125},[115,78580,78581],{"class":117,"line":2113},[115,78582,78583],{"class":125},"        }\n",[115,78585,78586],{"class":117,"line":2122},[115,78587,78588],{"class":125},"      }\n",[115,78590,78591],{"class":117,"line":2131},[115,78592,3403],{"class":125},[115,78594,78595],{"class":117,"line":2136},[115,78596,78504],{"class":125},[115,78598,78599,78601,78603,78606],{"class":117,"line":2142},[115,78600,78509],{"class":202},[115,78602,2513],{"class":125},[115,78604,78605],{"class":132},"\"MediaObjects\"",[115,78607,3354],{"class":125},[115,78609,78610,78612,78614,78616],{"class":117,"line":2273},[115,78611,78521],{"class":202},[115,78613,2513],{"class":125},[115,78615,78526],{"class":132},[115,78617,3354],{"class":125},[115,78619,78620,78622],{"class":117,"line":2282},[115,78621,78533],{"class":202},[115,78623,78499],{"class":125},[115,78625,78626,78629],{"class":117,"line":2291},[115,78627,78628],{"class":132},"        \"s3:GetObject\"",[115,78630,3354],{"class":125},[115,78632,78633,78636],{"class":117,"line":2299},[115,78634,78635],{"class":132},"        \"s3:PutObject\"",[115,78637,3354],{"class":125},[115,78639,78640,78643],{"class":117,"line":2307},[115,78641,78642],{"class":132},"        \"s3:DeleteObject\"",[115,78644,3354],{"class":125},[115,78646,78647],{"class":117,"line":2315},[115,78648,78649],{"class":132},"        \"s3:AbortMultipartUpload\"\n",[115,78651,78652],{"class":117,"line":2320},[115,78653,78654],{"class":125},"      ],\n",[115,78656,78657,78659,78661],{"class":117,"line":7083},[115,78658,78545],{"class":202},[115,78660,2513],{"class":125},[115,78662,78663],{"class":132},"\"arn:aws:s3:::myapp-prod-media\u002Fmedia\u002F*\"\n",[115,78665,78666],{"class":117,"line":7090},[115,78667,2233],{"class":125},[115,78669,78670],{"class":117,"line":7097},[115,78671,78672],{"class":125},"  ]\n",[115,78674,78675],{"class":117,"line":7108},[115,78676,2323],{"class":125},[16,78678,78679],{},"For larger uploads, multipart-related permissions may also be required depending on your upload path and client behavior.",[16,78681,78682],{},"Store credentials outside the repository:",[106,78684,78686],{"className":2329,"code":78685,"language":2331,"meta":111,"style":111},"AWS_ACCESS_KEY_ID=...\nAWS_SECRET_ACCESS_KEY=...\nAWS_STORAGE_BUCKET_NAME=myapp-prod-media\nAWS_S3_REGION_NAME=us-east-1\n",[20,78687,78688,78693,78698,78703],{"__ignoreMap":111},[115,78689,78690],{"class":117,"line":118},[115,78691,78692],{},"AWS_ACCESS_KEY_ID=...\n",[115,78694,78695],{"class":117,"line":136},[115,78696,78697],{},"AWS_SECRET_ACCESS_KEY=...\n",[115,78699,78700],{"class":117,"line":149},[115,78701,78702],{},"AWS_STORAGE_BUCKET_NAME=myapp-prod-media\n",[115,78704,78705],{"class":117,"line":162},[115,78706,78707],{},"AWS_S3_REGION_NAME=us-east-1\n",[16,78709,78710],{},"If you use IAM roles, you usually do not need to set access keys in Django environment variables.",[11,78712,78714],{"id":78713},"_4-install-django-s3-storage-dependencies","4) Install Django S3 storage dependencies",[16,78716,78717],{},"Install the required packages in the same environment used by production:",[106,78719,78721],{"className":108,"code":78720,"language":110,"meta":111,"style":111},"pip install django-storages boto3\n",[20,78722,78723],{"__ignoreMap":111},[115,78724,78725,78727,78729,78732],{"class":117,"line":118},[115,78726,8618],{"class":262},[115,78728,6600],{"class":132},[115,78730,78731],{"class":132}," django-storages",[115,78733,78734],{"class":132}," boto3\n",[16,78736,9761,78737,78739],{},[20,78738,28328],{}," entry:",[106,78741,78743],{"className":75350,"code":78742,"language":75352,"meta":111,"style":111},"django-storages>=1.14,\u003C2.0\nboto3>=1.34,\u003C2.0\n",[20,78744,78745,78750],{"__ignoreMap":111},[115,78746,78747],{"class":117,"line":118},[115,78748,78749],{},"django-storages>=1.14,\u003C2.0\n",[115,78751,78752],{"class":117,"line":136},[115,78753,78754],{},"boto3>=1.34,\u003C2.0\n",[16,78756,61697,78757,78760,78761,241],{},[20,78758,78759],{},"storages"," to ",[20,78762,18460],{},[106,78764,78766],{"className":2369,"code":78765,"language":1114,"meta":111,"style":111},"INSTALLED_APPS = [\n    # ...\n    \"storages\",\n]\n",[20,78767,78768,78776,78780,78787],{"__ignoreMap":111},[115,78769,78770,78772,78774],{"class":117,"line":118},[115,78771,18460],{"class":202},[115,78773,2380],{"class":121},[115,78775,3540],{"class":125},[115,78777,78778],{"class":117,"line":136},[115,78779,16643],{"class":3861},[115,78781,78782,78785],{"class":117,"line":149},[115,78783,78784],{"class":132},"    \"storages\"",[115,78786,3354],{"class":125},[115,78788,78789],{"class":117,"line":162},[115,78790,2552],{"class":125},[16,78792,3515],{},[106,78794,78796],{"className":108,"code":78795,"language":110,"meta":111,"style":111},"python -c \"import storages, boto3; print('ok')\"\n",[20,78797,78798],{"__ignoreMap":111},[115,78799,78800,78802,78804],{"class":117,"line":118},[115,78801,1114],{"class":262},[115,78803,1024],{"class":202},[115,78805,78806],{"class":132}," \"import storages, boto3; print('ok')\"\n",[11,78808,78810],{"id":78809},"_5-configure-django-media-storage-for-amazon-s3","5) Configure Django media storage for Amazon S3",[16,78812,78813,78814,211],{},"For Django 4.2+, use ",[20,78815,16659],{},[106,78817,78819],{"className":2369,"code":78818,"language":1114,"meta":111,"style":111},"import os\n\nAWS_STORAGE_BUCKET_NAME = os.environ[\"AWS_STORAGE_BUCKET_NAME\"]\nAWS_S3_REGION_NAME = os.environ[\"AWS_S3_REGION_NAME\"]\n\nAWS_DEFAULT_ACL = None\nAWS_S3_FILE_OVERWRITE = False\nAWS_LOCATION = \"media\"\n\n# Keep media private by default. Do not hardcode a public MEDIA_URL unless\n# you intentionally serve public objects.\nSTORAGES = {\n    \"default\": {\n        \"BACKEND\": \"storages.backends.s3.S3Storage\",\n        \"OPTIONS\": {\n            \"bucket_name\": AWS_STORAGE_BUCKET_NAME,\n            \"region_name\": AWS_S3_REGION_NAME,\n            \"default_acl\": AWS_DEFAULT_ACL,\n            \"file_overwrite\": AWS_S3_FILE_OVERWRITE,\n            \"location\": AWS_LOCATION,\n            \"querystring_auth\": True,\n        },\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"django.contrib.staticfiles.storage.StaticFilesStorage\",\n    },\n}\n",[20,78820,78821,78827,78831,78845,78859,78863,78873,78882,78891,78895,78900,78905,78913,78919,78930,78936,78947,78958,78969,78980,78991,79002,79006,79010,79016,79027,79031],{"__ignoreMap":111},[115,78822,78823,78825],{"class":117,"line":118},[115,78824,5613],{"class":121},[115,78826,5616],{"class":125},[115,78828,78829],{"class":117,"line":136},[115,78830,310],{"emptyLinePlaceholder":309},[115,78832,78833,78836,78838,78840,78843],{"class":117,"line":149},[115,78834,78835],{"class":202},"AWS_STORAGE_BUCKET_NAME",[115,78837,2380],{"class":121},[115,78839,8861],{"class":125},[115,78841,78842],{"class":132},"\"AWS_STORAGE_BUCKET_NAME\"",[115,78844,2552],{"class":125},[115,78846,78847,78850,78852,78854,78857],{"class":117,"line":162},[115,78848,78849],{"class":202},"AWS_S3_REGION_NAME",[115,78851,2380],{"class":121},[115,78853,8861],{"class":125},[115,78855,78856],{"class":132},"\"AWS_S3_REGION_NAME\"",[115,78858,2552],{"class":125},[115,78860,78861],{"class":117,"line":175},[115,78862,310],{"emptyLinePlaceholder":309},[115,78864,78865,78868,78870],{"class":117,"line":350},[115,78866,78867],{"class":202},"AWS_DEFAULT_ACL",[115,78869,2380],{"class":121},[115,78871,78872],{"class":202}," None\n",[115,78874,78875,78878,78880],{"class":117,"line":365},[115,78876,78877],{"class":202},"AWS_S3_FILE_OVERWRITE",[115,78879,2380],{"class":121},[115,78881,7355],{"class":202},[115,78883,78884,78887,78889],{"class":117,"line":380},[115,78885,78886],{"class":202},"AWS_LOCATION",[115,78888,2380],{"class":121},[115,78890,64225],{"class":132},[115,78892,78893],{"class":117,"line":487},[115,78894,310],{"emptyLinePlaceholder":309},[115,78896,78897],{"class":117,"line":2095},[115,78898,78899],{"class":3861},"# Keep media private by default. Do not hardcode a public MEDIA_URL unless\n",[115,78901,78902],{"class":117,"line":2104},[115,78903,78904],{"class":3861},"# you intentionally serve public objects.\n",[115,78906,78907,78909,78911],{"class":117,"line":2113},[115,78908,16659],{"class":202},[115,78910,2380],{"class":121},[115,78912,2166],{"class":125},[115,78914,78915,78917],{"class":117,"line":2122},[115,78916,10664],{"class":132},[115,78918,3374],{"class":125},[115,78920,78921,78923,78925,78928],{"class":117,"line":2131},[115,78922,16677],{"class":132},[115,78924,2513],{"class":125},[115,78926,78927],{"class":132},"\"storages.backends.s3.S3Storage\"",[115,78929,3354],{"class":125},[115,78931,78932,78934],{"class":117,"line":2136},[115,78933,10775],{"class":132},[115,78935,3374],{"class":125},[115,78937,78938,78941,78943,78945],{"class":117,"line":2142},[115,78939,78940],{"class":132},"            \"bucket_name\"",[115,78942,2513],{"class":125},[115,78944,78835],{"class":202},[115,78946,3354],{"class":125},[115,78948,78949,78952,78954,78956],{"class":117,"line":2273},[115,78950,78951],{"class":132},"            \"region_name\"",[115,78953,2513],{"class":125},[115,78955,78849],{"class":202},[115,78957,3354],{"class":125},[115,78959,78960,78963,78965,78967],{"class":117,"line":2282},[115,78961,78962],{"class":132},"            \"default_acl\"",[115,78964,2513],{"class":125},[115,78966,78867],{"class":202},[115,78968,3354],{"class":125},[115,78970,78971,78974,78976,78978],{"class":117,"line":2291},[115,78972,78973],{"class":132},"            \"file_overwrite\"",[115,78975,2513],{"class":125},[115,78977,78877],{"class":202},[115,78979,3354],{"class":125},[115,78981,78982,78985,78987,78989],{"class":117,"line":2299},[115,78983,78984],{"class":132},"            \"location\"",[115,78986,2513],{"class":125},[115,78988,78886],{"class":202},[115,78990,3354],{"class":125},[115,78992,78993,78996,78998,79000],{"class":117,"line":2307},[115,78994,78995],{"class":132},"            \"querystring_auth\"",[115,78997,2513],{"class":125},[115,78999,35949],{"class":202},[115,79001,3354],{"class":125},[115,79003,79004],{"class":117,"line":2315},[115,79005,3398],{"class":125},[115,79007,79008],{"class":117,"line":2320},[115,79009,3403],{"class":125},[115,79011,79012,79014],{"class":117,"line":7083},[115,79013,16669],{"class":132},[115,79015,3374],{"class":125},[115,79017,79018,79020,79022,79025],{"class":117,"line":7090},[115,79019,16677],{"class":132},[115,79021,2513],{"class":125},[115,79023,79024],{"class":132},"\"django.contrib.staticfiles.storage.StaticFilesStorage\"",[115,79026,3354],{"class":125},[115,79028,79029],{"class":117,"line":7097},[115,79030,3403],{"class":125},[115,79032,79033],{"class":117,"line":7108},[115,79034,2323],{"class":125},[16,79036,79037],{},"If you are on older Django versions, the media storage setting is typically:",[106,79039,79041],{"className":2369,"code":79040,"language":1114,"meta":111,"style":111},"DEFAULT_FILE_STORAGE = \"storages.backends.s3.S3Storage\"\n",[20,79042,79043],{"__ignoreMap":111},[115,79044,79045,79048,79050],{"class":117,"line":118},[115,79046,79047],{"class":202},"DEFAULT_FILE_STORAGE",[115,79049,2380],{"class":121},[115,79051,79052],{"class":132}," \"storages.backends.s3.S3Storage\"\n",[16,79054,79055],{},"Keep static storage separate. Do not point both static and media at the same path unless you have explicitly designed that layout.",[16,79057,79058,79059,79061,79062,79065],{},"If media should remain private, do not set a public ",[20,79060,15204],{}," to raw S3 objects. Use the S3 storage backend with signed URLs (",[20,79063,79064],{},"querystring_auth=True",") or generate presigned or authenticated download URLs in application code.",[52,79067,79069],{"id":79068},"optional-public-media-variant","Optional public-media variant",[16,79071,79072],{},"If your media files are intentionally public, configure that explicitly rather than using it as the default model.",[16,79074,79075],{},"Example public-media settings:",[106,79077,79079],{"className":2369,"code":79078,"language":1114,"meta":111,"style":111},"import os\n\nAWS_STORAGE_BUCKET_NAME = os.environ[\"AWS_STORAGE_BUCKET_NAME\"]\nAWS_S3_REGION_NAME = os.environ[\"AWS_S3_REGION_NAME\"]\n\nAWS_DEFAULT_ACL = None\nAWS_S3_FILE_OVERWRITE = False\nAWS_LOCATION = \"media\"\n\nSTORAGES = {\n    \"default\": {\n        \"BACKEND\": \"storages.backends.s3.S3Storage\",\n        \"OPTIONS\": {\n            \"bucket_name\": AWS_STORAGE_BUCKET_NAME,\n            \"region_name\": AWS_S3_REGION_NAME,\n            \"default_acl\": AWS_DEFAULT_ACL,\n            \"file_overwrite\": AWS_S3_FILE_OVERWRITE,\n            \"location\": AWS_LOCATION,\n            \"querystring_auth\": False,\n        },\n    },\n    \"staticfiles\": {\n        \"BACKEND\": \"django.contrib.staticfiles.storage.StaticFilesStorage\",\n    },\n}\n",[20,79080,79081,79087,79091,79103,79115,79119,79127,79135,79143,79147,79155,79161,79171,79177,79187,79197,79207,79217,79227,79237,79241,79245,79251,79261,79265],{"__ignoreMap":111},[115,79082,79083,79085],{"class":117,"line":118},[115,79084,5613],{"class":121},[115,79086,5616],{"class":125},[115,79088,79089],{"class":117,"line":136},[115,79090,310],{"emptyLinePlaceholder":309},[115,79092,79093,79095,79097,79099,79101],{"class":117,"line":149},[115,79094,78835],{"class":202},[115,79096,2380],{"class":121},[115,79098,8861],{"class":125},[115,79100,78842],{"class":132},[115,79102,2552],{"class":125},[115,79104,79105,79107,79109,79111,79113],{"class":117,"line":162},[115,79106,78849],{"class":202},[115,79108,2380],{"class":121},[115,79110,8861],{"class":125},[115,79112,78856],{"class":132},[115,79114,2552],{"class":125},[115,79116,79117],{"class":117,"line":175},[115,79118,310],{"emptyLinePlaceholder":309},[115,79120,79121,79123,79125],{"class":117,"line":350},[115,79122,78867],{"class":202},[115,79124,2380],{"class":121},[115,79126,78872],{"class":202},[115,79128,79129,79131,79133],{"class":117,"line":365},[115,79130,78877],{"class":202},[115,79132,2380],{"class":121},[115,79134,7355],{"class":202},[115,79136,79137,79139,79141],{"class":117,"line":380},[115,79138,78886],{"class":202},[115,79140,2380],{"class":121},[115,79142,64225],{"class":132},[115,79144,79145],{"class":117,"line":487},[115,79146,310],{"emptyLinePlaceholder":309},[115,79148,79149,79151,79153],{"class":117,"line":2095},[115,79150,16659],{"class":202},[115,79152,2380],{"class":121},[115,79154,2166],{"class":125},[115,79156,79157,79159],{"class":117,"line":2104},[115,79158,10664],{"class":132},[115,79160,3374],{"class":125},[115,79162,79163,79165,79167,79169],{"class":117,"line":2113},[115,79164,16677],{"class":132},[115,79166,2513],{"class":125},[115,79168,78927],{"class":132},[115,79170,3354],{"class":125},[115,79172,79173,79175],{"class":117,"line":2122},[115,79174,10775],{"class":132},[115,79176,3374],{"class":125},[115,79178,79179,79181,79183,79185],{"class":117,"line":2131},[115,79180,78940],{"class":132},[115,79182,2513],{"class":125},[115,79184,78835],{"class":202},[115,79186,3354],{"class":125},[115,79188,79189,79191,79193,79195],{"class":117,"line":2136},[115,79190,78951],{"class":132},[115,79192,2513],{"class":125},[115,79194,78849],{"class":202},[115,79196,3354],{"class":125},[115,79198,79199,79201,79203,79205],{"class":117,"line":2142},[115,79200,78962],{"class":132},[115,79202,2513],{"class":125},[115,79204,78867],{"class":202},[115,79206,3354],{"class":125},[115,79208,79209,79211,79213,79215],{"class":117,"line":2273},[115,79210,78973],{"class":132},[115,79212,2513],{"class":125},[115,79214,78877],{"class":202},[115,79216,3354],{"class":125},[115,79218,79219,79221,79223,79225],{"class":117,"line":2282},[115,79220,78984],{"class":132},[115,79222,2513],{"class":125},[115,79224,78886],{"class":202},[115,79226,3354],{"class":125},[115,79228,79229,79231,79233,79235],{"class":117,"line":2291},[115,79230,78995],{"class":132},[115,79232,2513],{"class":125},[115,79234,3364],{"class":202},[115,79236,3354],{"class":125},[115,79238,79239],{"class":117,"line":2299},[115,79240,3398],{"class":125},[115,79242,79243],{"class":117,"line":2307},[115,79244,3403],{"class":125},[115,79246,79247,79249],{"class":117,"line":2315},[115,79248,16669],{"class":132},[115,79250,3374],{"class":125},[115,79252,79253,79255,79257,79259],{"class":117,"line":2320},[115,79254,16677],{"class":132},[115,79256,2513],{"class":125},[115,79258,79024],{"class":132},[115,79260,3354],{"class":125},[115,79262,79263],{"class":117,"line":7083},[115,79264,3403],{"class":125},[115,79266,79267],{"class":117,"line":7090},[115,79268,2323],{"class":125},[16,79270,79271],{},"If you choose this model, make sure your bucket policy and public access settings are intentionally configured for public reads.",[11,79273,79275],{"id":79274},"_6-secure-the-media-storage-configuration","6) Secure the media storage configuration",[16,79277,79278],{},"Recommended settings and practices:",[63,79280,79281,79287,79293,79296,79299],{},[66,79282,79283,79286],{},[20,79284,79285],{},"AWS_DEFAULT_ACL = None"," to avoid relying on legacy ACL behavior",[66,79288,79289,79292],{},[20,79290,79291],{},"AWS_S3_FILE_OVERWRITE = False"," to reduce accidental filename collisions",[66,79294,79295],{},"validate file size and content type in Django forms, serializers, or views",[66,79297,79298],{},"keep access keys in secret storage or use IAM roles",[66,79300,79301],{},"keep bucket public access blocked unless you intentionally serve public media",[16,79303,79304],{},"If uploads contain private or sensitive files, keep the bucket private and use presigned URLs or an authenticated download view. Do not make the whole bucket public just to simplify file delivery.",[11,79306,79308],{"id":79307},"_7-migrate-existing-media-files-to-s3","7) Migrate existing media files to S3",[16,79310,79311,79312,211],{},"Audit where your current media files live, usually under ",[20,79313,15214],{},[16,79315,79316],{},"Dry-run the sync first:",[106,79318,79320],{"className":108,"code":79319,"language":110,"meta":111,"style":111},"aws s3 sync \u002Fpath\u002Fto\u002Fcurrent\u002Fmedia s3:\u002F\u002Fmyapp-prod-media\u002Fmedia\u002F --dryrun\n",[20,79321,79322],{"__ignoreMap":111},[115,79323,79324,79327,79330,79333,79336,79339],{"class":117,"line":118},[115,79325,79326],{"class":262},"aws",[115,79328,79329],{"class":132}," s3",[115,79331,79332],{"class":132}," sync",[115,79334,79335],{"class":132}," \u002Fpath\u002Fto\u002Fcurrent\u002Fmedia",[115,79337,79338],{"class":132}," s3:\u002F\u002Fmyapp-prod-media\u002Fmedia\u002F",[115,79340,79341],{"class":202}," --dryrun\n",[16,79343,79344],{},"If the output looks correct, run the real sync:",[106,79346,79348],{"className":108,"code":79347,"language":110,"meta":111,"style":111},"aws s3 sync \u002Fpath\u002Fto\u002Fcurrent\u002Fmedia s3:\u002F\u002Fmyapp-prod-media\u002Fmedia\u002F\n",[20,79349,79350],{"__ignoreMap":111},[115,79351,79352,79354,79356,79358,79360],{"class":117,"line":118},[115,79353,79326],{"class":262},[115,79355,79329],{"class":132},[115,79357,79332],{"class":132},[115,79359,79335],{"class":132},[115,79361,79362],{"class":132}," s3:\u002F\u002Fmyapp-prod-media\u002Fmedia\u002F\n",[16,79364,79365],{},"Verify objects were copied:",[106,79367,79369],{"className":108,"code":79368,"language":110,"meta":111,"style":111},"aws s3 ls s3:\u002F\u002Fmyapp-prod-media\u002Fmedia\u002F --recursive | head\n",[20,79370,79371],{"__ignoreMap":111},[115,79372,79373,79375,79377,79379,79381,79384,79386],{"class":117,"line":118},[115,79374,79326],{"class":262},[115,79376,79329],{"class":132},[115,79378,12758],{"class":132},[115,79380,79338],{"class":132},[115,79382,79383],{"class":202}," --recursive",[115,79385,579],{"class":121},[115,79387,582],{"class":262},[16,79389,79390],{},"Cutover notes:",[63,79392,79393,79396,79399],{},[66,79394,79395],{},"copy existing files before enabling S3-backed writes",[66,79397,79398],{},"avoid deployment during high upload activity if possible",[66,79400,79401],{},"if uploads continue during migration, require either a short upload freeze during cutover or a final sync pass immediately before switching writes to S3",[16,79403,79404],{},"Rollback safety:",[63,79406,79407,79410,79413],{},[66,79408,79409],{},"keep the original local media directory intact until verification is complete",[66,79411,79412],{},"do not delete local files immediately after cutover",[66,79414,79415],{},"remember that files uploaded after S3 cutover may exist only in S3; a rollback to local storage can orphan those uploads unless you freeze uploads or sync them back",[11,79417,79419],{"id":79418},"_8-deploy-the-configuration-safely","8) Deploy the configuration safely",[16,79421,79422],{},"Update environment variables in your deployment system, then restart Django processes.",[16,79424,79425],{},"Typical sequence:",[1173,79427,79428,79431,79434,79437,79440],{},[66,79429,79430],{},"add bucket and credential settings",[66,79432,79433],{},"deploy code with S3 storage config",[66,79435,79436],{},"restart web and worker processes",[66,79438,79439],{},"test uploads and reads",[66,79441,79442],{},"check logs",[16,79444,79445],{},"Smoke tests:",[63,79447,79448,79451,79454,79457],{},[66,79449,79450],{},"upload a file through Django admin or your app UI",[66,79452,79453],{},"open the returned file URL",[66,79455,79456],{},"confirm the object appears in the expected S3 prefix",[66,79458,79459],{},"confirm an older uploaded file still resolves",[16,79461,79462],{},"You can also test from the Django shell:",[106,79464,79465],{"className":108,"code":6059,"language":110,"meta":111,"style":111},[20,79466,79467],{"__ignoreMap":111},[115,79468,79469,79471,79473],{"class":117,"line":118},[115,79470,1114],{"class":262},[115,79472,1117],{"class":132},[115,79474,6070],{"class":132},[106,79476,79478],{"className":2369,"code":79477,"language":1114,"meta":111,"style":111},"from django.core.files.base import ContentFile\nfrom myapp.models import MyModel\n\nobj = MyModel.objects.create()\nobj.file.save(\"s3-test.txt\", ContentFile(b\"storage test\"), save=True)\n\nprint(obj.file.name)\nprint(obj.file.url)\n",[20,79479,79480,79492,79504,79508,79518,79547,79551,79558],{"__ignoreMap":111},[115,79481,79482,79484,79487,79489],{"class":117,"line":118},[115,79483,5621],{"class":121},[115,79485,79486],{"class":125}," django.core.files.base ",[115,79488,5613],{"class":121},[115,79490,79491],{"class":125}," ContentFile\n",[115,79493,79494,79496,79499,79501],{"class":117,"line":136},[115,79495,5621],{"class":121},[115,79497,79498],{"class":125}," myapp.models ",[115,79500,5613],{"class":121},[115,79502,79503],{"class":125}," MyModel\n",[115,79505,79506],{"class":117,"line":149},[115,79507,310],{"emptyLinePlaceholder":309},[115,79509,79510,79513,79515],{"class":117,"line":162},[115,79511,79512],{"class":125},"obj ",[115,79514,129],{"class":121},[115,79516,79517],{"class":125}," MyModel.objects.create()\n",[115,79519,79520,79523,79526,79529,79532,79535,79538,79541,79543,79545],{"class":117,"line":175},[115,79521,79522],{"class":125},"obj.file.save(",[115,79524,79525],{"class":132},"\"s3-test.txt\"",[115,79527,79528],{"class":125},", ContentFile(",[115,79530,79531],{"class":121},"b",[115,79533,79534],{"class":132},"\"storage test\"",[115,79536,79537],{"class":125},"), ",[115,79539,79540],{"class":5680},"save",[115,79542,129],{"class":121},[115,79544,35949],{"class":202},[115,79546,2394],{"class":125},[115,79548,79549],{"class":117,"line":350},[115,79550,310],{"emptyLinePlaceholder":309},[115,79552,79553,79555],{"class":117,"line":365},[115,79554,6102],{"class":202},[115,79556,79557],{"class":125},"(obj.file.name)\n",[115,79559,79560,79562],{"class":117,"line":380},[115,79561,6102],{"class":202},[115,79563,79564],{"class":125},"(obj.file.url)\n",[16,79566,79567],{},"Then verify the object exists in S3:",[106,79569,79571],{"className":108,"code":79570,"language":110,"meta":111,"style":111},"aws s3api head-object --bucket myapp-prod-media --key media\u002Fs3-test.txt\n",[20,79572,79573],{"__ignoreMap":111},[115,79574,79575,79577,79580,79583,79586,79589,79592],{"class":117,"line":118},[115,79576,79326],{"class":262},[115,79578,79579],{"class":132}," s3api",[115,79581,79582],{"class":132}," head-object",[115,79584,79585],{"class":202}," --bucket",[115,79587,79588],{"class":132}," myapp-prod-media",[115,79590,79591],{"class":202}," --key",[115,79593,79594],{"class":132}," media\u002Fs3-test.txt\n",[16,79596,79597],{},"If your model requires other fields, use a model that already contains a file field and can be created safely in your environment.",[16,79599,79600],{},"If URLs or access fail, stop here and fix configuration before continuing traffic changes.",[11,79602,79604],{"id":79603},"_9-validate-the-production-setup","9) Validate the production setup",[16,79606,79607],{},"After deployment, verify all of the following:",[63,79609,79610,79613,79618,79621,79624],{},[66,79611,79612],{},"new uploads succeed",[66,79614,79615,79616],{},"uploaded objects appear under ",[20,79617,78431],{},[66,79619,79620],{},"generated URLs match your intended access model",[66,79622,79623],{},"old database records still resolve to valid files",[66,79625,79626,79627,79630],{},"application logs show no ",[20,79628,79629],{},"AccessDenied",", credential, or region errors",[16,79632,79633],{},"Common AWS CLI check:",[106,79635,79637],{"className":108,"code":79636,"language":110,"meta":111,"style":111},"aws s3api head-object --bucket myapp-prod-media --key media\u002Fexample.jpg\n",[20,79638,79639],{"__ignoreMap":111},[115,79640,79641,79643,79645,79647,79649,79651,79653],{"class":117,"line":118},[115,79642,79326],{"class":262},[115,79644,79579],{"class":132},[115,79646,79582],{"class":132},[115,79648,79585],{"class":202},[115,79650,79588],{"class":132},[115,79652,79591],{"class":202},[115,79654,79655],{"class":132}," media\u002Fexample.jpg\n",[16,79657,79658],{},"If this fails for a known uploaded object, check IAM policy scope, region, and key path.",[11,79660,79662],{"id":79661},"_10-rollback-and-recovery-plan","10) Rollback and recovery plan",[16,79664,79665],{},"If new uploads fail after deployment:",[1173,79667,79668,79671,79674,79677],{},[66,79669,79670],{},"revert Django storage settings to the previous backend",[66,79672,79673],{},"revert related environment variable changes",[66,79675,79676],{},"redeploy and restart application processes",[66,79678,79679],{},"continue serving existing local media while investigating",[16,79681,79682],{},"Before rolling back, decide what to do with files uploaded after S3 cutover. Those files may exist only in S3. If you switch back to local storage without an upload freeze or reverse sync plan, users may lose access to newly uploaded files.",[16,79684,79685],{},"Keep a backup of local media until S3 storage has been stable in production.",[16,79687,79688],{},"If S3 versioning is enabled, you can recover from accidental overwrite or deletion from the bucket history. That does not replace application-level rollback, but it helps recover objects.",[11,79690,1321],{"id":1320},[16,79692,79693],{},"This setup works because Django stops treating the app server filesystem as the source of truth for user uploads. Instead, uploaded files are stored in object storage that all app instances can access consistently.",[16,79695,79696],{},"For most production deployments, S3 is a better fit than local disk because it survives instance replacement and scales without shared filesystem management. The tradeoff is that object storage configuration, IAM, and access patterns must be correct. That is why least-privilege IAM, explicit media prefixes, and post-deploy verification matter.",[16,79698,79699],{},"Use alternatives such as shared volumes only when you have a clear operational reason and understand the scaling and recovery limits.",[11,79701,1337],{"id":1336},[63,79703,79704,79710,79719,79725,79734,79740],{},[66,79705,79706,79709],{},[1226,79707,79708],{},"Static and media mixing:"," keep them separate in settings and bucket paths.",[66,79711,79712,79715,79716,79718],{},[1226,79713,79714],{},"Wrong region:"," a mismatched ",[20,79717,78849],{}," can cause failed requests or bad URLs.",[66,79720,79721,79724],{},[1226,79722,79723],{},"Access denied on file URL:"," your bucket policy, object access model, or signed URL configuration does not match your intended setup.",[66,79726,79727,79730,79731,79733],{},[1226,79728,79729],{},"Filename collisions:"," without ",[20,79732,79291],{},", uploads with the same name may replace earlier files.",[66,79735,79736,79739],{},[1226,79737,79738],{},"Existing local files missing:"," switching storage does not automatically copy old uploads.",[66,79741,79742,79745],{},[1226,79743,79744],{},"Direct browser uploads:"," if you later move to browser-to-S3 uploads, you will also need CORS, signed upload policies, and a different validation flow.",[52,79747,2908],{"id":2907},[16,79749,79750],{},"If you repeat this setup across projects, the same pieces are usually duplicated: bucket config, IAM policy, Django settings, environment variables, and migration checks. Those are good candidates for reusable templates or deployment scripts. The manual setup is still worth understanding first so you can verify what automation is doing.",[11,79752,1386],{"id":1385},[16,79754,79755,79756,211],{},"If you need the underlying distinction first, read ",[1226,79757,79758],{},[1395,79759,43404],{"href":2978},[16,79761,56930],{},[63,79763,79764,79771],{},[66,79765,79766],{},[1226,79767,79768],{},[1395,79769,79770],{"href":2985},"How to deploy Django with Gunicorn and Nginx on Ubuntu",[66,79772,79773],{},[1226,79774,79775],{},[1395,79776,8039],{"href":8038},[16,79778,79779,79780,211],{},"For broader production rollout checks, see ",[1226,79781,79782],{},[1395,79783,3000],{"href":2999},[16,79785,79786,79787,211],{},"If uploads fail after deployment, see ",[1226,79788,79789],{},[1395,79790,79791],{"href":50766},"Why Django file uploads fail in production and how to fix them",[11,79793,1420],{"id":1419},[52,79795,79797],{"id":79796},"should-django-static-files-and-media-files-use-the-same-s3-bucket","Should Django static files and media files use the same S3 bucket?",[16,79799,79800],{},"They can, but separate buckets or at least separate prefixes are usually safer. The important rule is not to mix static and media into the same storage path or policy model.",[52,79802,79804],{"id":79803},"do-i-need-public-bucket-access-for-django-media-files","Do I need public bucket access for Django media files?",[16,79806,79807],{},"No. Public access is only needed if you want public object URLs. For private files, keep the bucket private and use signed URLs or authenticated download views.",[52,79809,79811],{"id":79810},"how-do-i-move-existing-media-files-to-s3-without-breaking-uploads","How do I move existing media files to S3 without breaking uploads?",[16,79813,79814],{},"Sync existing files first, verify object paths, then deploy the new storage settings. If uploads are active during migration, use a short freeze window or run a final sync pass immediately before switching writes.",[52,79816,79818],{"id":79817},"can-i-use-s3-with-docker-based-django-deployments","Can I use S3 with Docker-based Django deployments?",[16,79820,79821],{},"Yes. This is one of the most common reasons to use S3 for media. Containers should not be trusted as persistent media storage.",[52,79823,79825],{"id":79824},"what-should-come-from-iam-roles-instead-of-environment-variables","What should come from IAM roles instead of environment variables?",[16,79827,79828],{},"If your app runs on AWS infrastructure that supports roles, prefer IAM roles for S3 access and avoid long-lived access keys. Keep non-secret values like bucket name and region in environment variables.",[1485,79830,79831],{},"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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":149,"depth":149,"links":79833},[79834,79835,79836,79837,79842,79843,79844,79845,79848,79849,79850,79851,79852,79853,79854,79857,79858],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":78327,"depth":136,"text":78328,"children":79838},[79839,79840,79841],{"id":78331,"depth":149,"text":78332},{"id":78359,"depth":149,"text":78360},{"id":78386,"depth":149,"text":78387},{"id":78393,"depth":136,"text":78394},{"id":78453,"depth":136,"text":78454},{"id":78713,"depth":136,"text":78714},{"id":78809,"depth":136,"text":78810,"children":79846},[79847],{"id":79068,"depth":149,"text":79069},{"id":79274,"depth":136,"text":79275},{"id":79307,"depth":136,"text":79308},{"id":79418,"depth":136,"text":79419},{"id":79603,"depth":136,"text":79604},{"id":79661,"depth":136,"text":79662},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337,"children":79855},[79856],{"id":2907,"depth":149,"text":2908},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":79859},[79860,79861,79862,79863,79864],{"id":79796,"depth":149,"text":79797},{"id":79803,"depth":149,"text":79804},{"id":79810,"depth":149,"text":79811},{"id":79817,"depth":149,"text":79818},{"id":79824,"depth":149,"text":79825},{},"\u002Fstore-django-media-files-on-s3","19",[2985,8038,8045],{"title":78256,"description":78263},[1557,79871,78304],"aws-s3","store-django-media-files-on-s3",[1557,79871,78304],"Y2P7MdMslF3fwtuIEPcgrnUqIHkgsYZcWpgqC9JYeFI",{"id":79876,"title":79877,"body":79878,"category":1541,"description":82056,"difficulty":1543,"extension":1544,"funnel_stage":23036,"intent":1546,"meta":82057,"navigation":309,"path":82058,"priority":27130,"related":82059,"role":1553,"section":1554,"seo":82060,"stack":82061,"stem":82062,"tags":82063,"type":1561,"__hash__":82064},"articles\u002Fdjango-deployment-script-bash-guide.md","How to Write a Django Deployment Bash Script",{"type":8,"value":79879,"toc":82018},[79880,79882,79885,79888,79911,79914,79916,79922,79964,79967,79969,79973,79977,79980,80001,80004,80087,80091,80094,80120,80123,80127,80140,80146,80150,80154,80157,80187,80191,80197,80247,80250,80254,80257,80283,80287,80298,81341,81344,81358,81361,81374,81376,81388,81396,81400,81406,81409,81428,81432,81437,81478,81481,81485,81492,81496,81501,81505,81514,81518,81521,81524,81549,81554,81577,81580,81585,81589,81592,81595,81655,81658,81664,81668,81671,81686,81689,81704,81710,81723,81726,81737,81740,81742,81745,81786,81789,81791,81811,81814,81841,81843,81846,81849,81852,81875,81878,81880,81937,81939,81966,81968,81972,81975,81979,81982,81986,81996,82000,82003,82007,82015],[11,79881,14],{"id":13},[16,79883,79884],{},"A manual Django deployment often looks simple at first: SSH into a server, pull code, install dependencies, run migrations, collect static files, restart Gunicorn, and hope the app comes back cleanly.",[16,79886,79887],{},"That process becomes error-prone quickly. Common failures include:",[63,79889,79890,79893,79899,79902,79905,79908],{},[66,79891,79892],{},"deploying unreviewed branch state instead of a pinned revision",[66,79894,79895,79896,79898],{},"running ",[20,79897,10296],{}," against the wrong settings or environment",[66,79900,79901],{},"restarting the app before migrations finish",[66,79903,79904],{},"collecting static files into the wrong path",[66,79906,79907],{},"forgetting to restart Celery workers after a release",[66,79909,79910],{},"running two deploys at the same time and leaving the app in a partial state",[16,79912,79913],{},"A Django deployment Bash script should automate repeatable release steps and fail early when something is wrong. It should not provision servers, create databases, issue TLS certificates, or store secrets directly in the script. Those belong in separate infrastructure and secrets management processes.",[11,79915,30],{"id":29},[16,79917,79918,79919,11703],{},"The safest pattern for a ",[1226,79920,79921],{},"Django deployment Bash script",[1173,79923,79924,79927,79932,79935,79938,79941,79945,79949,79953,79958,79961],{},[66,79925,79926],{},"deploy a specific Git commit or tag",[66,79928,44234,79929],{},[20,79930,79931],{},"set -euo pipefail",[66,79933,79934],{},"validate required paths and variables before doing anything",[66,79936,79937],{},"activate the correct virtualenv",[66,79939,79940],{},"load production settings and required environment variables",[66,79942,7902,79943],{},[20,79944,15970],{},[66,79946,7902,79947],{},[20,79948,27190],{},[66,79950,7902,79951],{},[20,79952,27195],{},[66,79954,79955,79956],{},"reload or restart Gunicorn\u002FUvicorn through ",[20,79957,1277],{},[66,79959,79960],{},"run a health check with timeout and retries",[66,79962,79963],{},"keep logs and a rollback path",[16,79965,79966],{},"Bash is enough when you have one server or a small number of similar servers and a predictable release process. Move to a fuller release workflow when you need multi-server ordering, release directories, stronger rollback guarantees, or standardized deploys across multiple Django apps.",[11,79968,43],{"id":42},[11,79970,79972],{"id":79971},"define-the-scope-of-your-django-deployment-bash-script","Define the scope of your Django deployment Bash script",[52,79974,79976],{"id":79975},"choose-the-deployment-style-in-place-vs-release-directories","Choose the deployment style: in-place vs release directories",[16,79978,79979],{},"For a production-safe Bash script, use one of these patterns:",[63,79981,79982,79988],{},[66,79983,79984,79987],{},[1226,79985,79986],{},"In-place deploy",": update a checked-out repo on the server, then restart services",[66,79989,79990,79993,79994,79997,79998,80000],{},[1226,79991,79992],{},"Release directories",": create ",[20,79995,79996],{},"\u002Fsrv\u002Fmyapp\u002Freleases\u002F\u003Ctimestamp>",", then point ",[20,79999,14814],{}," at the active release",[16,80002,80003],{},"Release directories are safer for rollback. Recommended layout:",[106,80005,80007],{"className":108,"code":80006,"language":110,"meta":111,"style":111},"\u002Fsrv\u002Fmyapp\u002F\n├── current -> \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000\n├── releases\u002F\n│   ├── 20260423-101500\n│   └── 20260424-120000\n└── shared\u002F\n    ├── media\u002F\n    ├── logs\u002F\n    └── .env\n",[20,80008,80009,80014,80029,80036,80047,80056,80064,80072,80079],{"__ignoreMap":111},[115,80010,80011],{"class":117,"line":118},[115,80012,80013],{"class":262},"\u002Fsrv\u002Fmyapp\u002F\n",[115,80015,80016,80019,80022,80024,80026],{"class":117,"line":136},[115,80017,80018],{"class":262},"├──",[115,80020,80021],{"class":132}," current",[115,80023,4009],{"class":125},[115,80025,22818],{"class":121},[115,80027,80028],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260424-120000\n",[115,80030,80031,80033],{"class":117,"line":149},[115,80032,80018],{"class":262},[115,80034,80035],{"class":132}," releases\u002F\n",[115,80037,80038,80041,80044],{"class":117,"line":162},[115,80039,80040],{"class":262},"│",[115,80042,80043],{"class":132},"   ├──",[115,80045,80046],{"class":132}," 20260423-101500\n",[115,80048,80049,80051,80054],{"class":117,"line":175},[115,80050,80040],{"class":262},[115,80052,80053],{"class":132},"   └──",[115,80055,14849],{"class":132},[115,80057,80058,80061],{"class":117,"line":350},[115,80059,80060],{"class":262},"└──",[115,80062,80063],{"class":132}," shared\u002F\n",[115,80065,80066,80069],{"class":117,"line":365},[115,80067,80068],{"class":262},"    ├──",[115,80070,80071],{"class":132}," media\u002F\n",[115,80073,80074,80076],{"class":117,"line":380},[115,80075,80068],{"class":262},[115,80077,80078],{"class":132}," logs\u002F\n",[115,80080,80081,80084],{"class":117,"line":487},[115,80082,80083],{"class":262},"    └──",[115,80085,80086],{"class":132}," .env\n",[52,80088,80090],{"id":80089},"decide-which-steps-belong-in-the-script","Decide which steps belong in the script",[16,80092,80093],{},"Your script should include:",[63,80095,80096,80099,80101,80104,80107,80109,80112,80115,80117],{},[66,80097,80098],{},"target revision selection",[66,80100,31667],{},[66,80102,80103],{},"Django checks",[66,80105,80106],{},"migrations",[66,80108,65719],{},[66,80110,80111],{},"service reload or restart",[66,80113,80114],{},"health verification",[66,80116,37148],{},[66,80118,80119],{},"deploy locking",[16,80121,80122],{},"Keep infrastructure provisioning, database creation, certificate issuance, and secret generation out of the deploy script.",[52,80124,80126],{"id":80125},"keep-secrets-and-infrastructure-provisioning-out-of-the-script","Keep secrets and infrastructure provisioning out of the script",[16,80128,80129,80130,80132,80133,80135,80136,80139],{},"Do not hardcode values like ",[20,80131,2713],{},", database passwords, or API tokens in Bash. Read configuration from environment files managed outside the repo, ",[20,80134,1277],{}," unit ",[20,80137,80138],{},"EnvironmentFile=",", or another existing secret mechanism.",[16,80141,80142,80143,80145],{},"If you load an env file with ",[20,80144,5311],{},", only do that for a file you control administratively. Bash will execute its contents as shell code.",[11,80147,80149],{"id":80148},"prepare-the-server-and-application-layout","Prepare the server and application layout",[52,80151,80153],{"id":80152},"recommended-directory-structure","Recommended directory structure",[16,80155,80156],{},"Create a dedicated deploy location:",[106,80158,80160],{"className":108,"code":80159,"language":110,"meta":111,"style":111},"sudo mkdir -p \u002Fsrv\u002Fmyapp\u002Freleases \u002Fsrv\u002Fmyapp\u002Fshared \u002Fsrv\u002Fmyapp\u002Fshared\u002Flogs\nsudo mkdir -p \u002Fsrv\u002Fmyapp\u002Fshared\u002Fmedia\n",[20,80161,80162,80177],{"__ignoreMap":111},[115,80163,80164,80166,80168,80170,80172,80174],{"class":117,"line":118},[115,80165,2001],{"class":262},[115,80167,6721],{"class":132},[115,80169,1001],{"class":202},[115,80171,14777],{"class":132},[115,80173,14780],{"class":132},[115,80175,80176],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fshared\u002Flogs\n",[115,80178,80179,80181,80183,80185],{"class":117,"line":136},[115,80180,2001],{"class":262},[115,80182,6721],{"class":132},[115,80184,1001],{"class":202},[115,80186,14786],{"class":132},[52,80188,80190],{"id":80189},"dedicated-deploy-user-and-permissions","Dedicated deploy user and permissions",[16,80192,80193,80194,241],{},"Use a dedicated account instead of deploying as ",[20,80195,80196],{},"root",[106,80198,80200],{"className":108,"code":80199,"language":110,"meta":111,"style":111},"sudo adduser --system --group --home \u002Fsrv\u002Fmyapp deploy-myapp\nsudo chown -R deploy-myapp:deploy-myapp \u002Fsrv\u002Fmyapp\nsudo chmod -R 750 \u002Fsrv\u002Fmyapp\n",[20,80201,80202,80222,80235],{"__ignoreMap":111},[115,80203,80204,80206,80208,80211,80214,80217,80219],{"class":117,"line":118},[115,80205,2001],{"class":262},[115,80207,14212],{"class":132},[115,80209,80210],{"class":202}," --system",[115,80212,80213],{"class":202}," --group",[115,80215,80216],{"class":202}," --home",[115,80218,65135],{"class":132},[115,80220,80221],{"class":132}," deploy-myapp\n",[115,80223,80224,80226,80228,80230,80233],{"class":117,"line":136},[115,80225,2001],{"class":262},[115,80227,6733],{"class":132},[115,80229,6736],{"class":202},[115,80231,80232],{"class":132}," deploy-myapp:deploy-myapp",[115,80234,14799],{"class":132},[115,80236,80237,80239,80241,80243,80245],{"class":117,"line":149},[115,80238,2001],{"class":262},[115,80240,12480],{"class":132},[115,80242,6736],{"class":202},[115,80244,36899],{"class":202},[115,80246,14799],{"class":132},[16,80248,80249],{},"Your app service can run as a separate least-privilege user if needed, but the deploy user must have write access to release directories and read access to the shared config it needs.",[52,80251,80253],{"id":80252},"virtual-environment-and-dependency-installation-path","Virtual environment and dependency installation path",[16,80255,80256],{},"Keep the virtualenv outside the release directory so it survives deploy switches:",[106,80258,80260],{"className":108,"code":80259,"language":110,"meta":111,"style":111},"python3 -m venv \u002Fsrv\u002Fmyapp\u002Fvenv\n\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip install --upgrade pip\n",[20,80261,80262,80272],{"__ignoreMap":111},[115,80263,80264,80266,80268,80270],{"class":117,"line":118},[115,80265,12281],{"class":262},[115,80267,12284],{"class":202},[115,80269,12287],{"class":132},[115,80271,27493],{"class":132},[115,80273,80274,80277,80279,80281],{"class":117,"line":136},[115,80275,80276],{"class":262},"\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpip",[115,80278,6600],{"class":132},[115,80280,12338],{"class":202},[115,80282,12341],{"class":132},[11,80284,80286],{"id":80285},"write-a-minimal-django-deployment-bash-script","Write a minimal Django deployment Bash script",[16,80288,80289,80290,80293,80294,80297],{},"Save this as ",[20,80291,80292],{},"deploy.sh"," in your repo or in ",[20,80295,80296],{},"\u002Fsrv\u002Fmyapp\u002Fbin\u002F",", and make it executable.",[106,80299,80301],{"className":108,"code":80300,"language":110,"meta":111,"style":111},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nAPP_NAME=\"myapp\"\nAPP_ROOT=\"\u002Fsrv\u002Fmyapp\"\nRELEASES_DIR=\"$APP_ROOT\u002Freleases\"\nCURRENT_LINK=\"$APP_ROOT\u002Fcurrent\"\nSHARED_DIR=\"$APP_ROOT\u002Fshared\"\nVENV_PATH=\"$APP_ROOT\u002Fvenv\"\nSERVICE_NAME=\"gunicorn-myapp\"\nREPO_URL=\"git@github.com:example\u002Fmyapp.git\"\nSETTINGS_MODULE=\"myapp.settings.production\"\nTARGET_REVISION=\"${1:-}\"\n\nif [[ -z \"$TARGET_REVISION\" ]]; then\n  echo \"Usage: $0 \u003Cgit-commit-or-tag>\"\n  exit 1\nfi\n\nfor path in \"$APP_ROOT\" \"$RELEASES_DIR\" \"$SHARED_DIR\" \"$VENV_PATH\"; do\n  [[ -e \"$path\" ]] || { echo \"Missing required path: $path\"; exit 1; }\ndone\n\n[[ -f \"$SHARED_DIR\u002F.env\" ]] || { echo \"Missing env file: $SHARED_DIR\u002F.env\"; exit 1; }\n\nexec 9>\"$APP_ROOT\u002Fdeploy.lock\"\nflock -n 9 || { echo \"Another deployment is already running\"; exit 1; }\n\nTIMESTAMP=\"$(date +%Y%m%d-%H%M%S)\"\nNEW_RELEASE=\"$RELEASES_DIR\u002F$TIMESTAMP\"\nLOG_FILE=\"$SHARED_DIR\u002Flogs\u002Fdeploy-$TIMESTAMP.log\"\nPREVIOUS_RELEASE=\"$(readlink -f \"$CURRENT_LINK\" || true)\"\n\nexec > >(tee -a \"$LOG_FILE\") 2>&1\n\nrollback() {\n  echo \"Deployment failed\"\n  if [[ -n \"${PREVIOUS_RELEASE:-}\" && -d \"${PREVIOUS_RELEASE:-}\" ]]; then\n    echo \"Rolling back to $PREVIOUS_RELEASE\"\n    ln -sfn \"$PREVIOUS_RELEASE\" \"$CURRENT_LINK\"\n    sudo systemctl restart \"$SERVICE_NAME\"\n  fi\n}\n\ntrap rollback ERR\n\necho \"Starting deployment at $TIMESTAMP\"\necho \"Target revision: $TARGET_REVISION\"\n\ngit clone \"$REPO_URL\" \"$NEW_RELEASE\"\ncd \"$NEW_RELEASE\"\ngit fetch --tags origin\ngit checkout --detach \"$TARGET_REVISION\"\n\nln -sfn \"$SHARED_DIR\u002Fmedia\" \"$NEW_RELEASE\u002Fmedia\"\n\nsource \"$VENV_PATH\u002Fbin\u002Factivate\"\nexport DJANGO_SETTINGS_MODULE=\"$SETTINGS_MODULE\"\n\nset -a\nsource \"$SHARED_DIR\u002F.env\"\nset +a\n\n: \"${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required}\"\n: \"${DATABASE_URL:?DATABASE_URL is required}\"\n\npip install -r requirements.txt\n\npython manage.py check --deploy\npython manage.py migrate --noinput\npython manage.py collectstatic --noinput\n\nln -sfn \"$NEW_RELEASE\" \"$CURRENT_LINK\"\n\nsudo systemctl reload \"$SERVICE_NAME\" || sudo systemctl restart \"$SERVICE_NAME\"\nsudo systemctl status \"$SERVICE_NAME\" --no-pager\n\ncurl --fail --silent --show-error --max-time 10 --retry 5 --retry-delay 2 https:\u002F\u002Fexample.com\u002Fhealth\u002F\n\necho \"Deployment successful: $NEW_RELEASE\"\necho \"Previous release was: ${PREVIOUS_RELEASE:-none}\"\n\ntrap - ERR\n",[20,80302,80303,80308,80318,80334,80338,80347,80357,80372,80386,80400,80414,80424,80434,80444,80462,80466,80488,80501,80508,80512,80516,80556,80600,80605,80609,80647,80651,80665,80692,80696,80711,80729,80748,80777,80781,80806,80810,80817,80824,80858,80871,80890,80906,80911,80915,80919,80930,80934,80945,80956,80960,80980,80990,81003,81018,81022,81042,81046,81057,81072,81076,81082,81093,81099,81103,81125,81143,81147,81157,81161,81171,81181,81191,81195,81213,81217,81245,81261,81265,81294,81298,81310,81327,81332],{"__ignoreMap":111},[115,80304,80305],{"class":117,"line":118},[115,80306,80307],{"class":3861},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[115,80309,80310,80312,80315],{"class":117,"line":136},[115,80311,203],{"class":202},[115,80313,80314],{"class":202}," -euo",[115,80316,80317],{"class":132}," pipefail\n",[115,80319,80320,80323,80325,80328,80331],{"class":117,"line":149},[115,80321,80322],{"class":125},"IFS",[115,80324,129],{"class":121},[115,80326,80327],{"class":132},"$'",[115,80329,80330],{"class":202},"\\n\\t",[115,80332,80333],{"class":132},"'\n",[115,80335,80336],{"class":117,"line":162},[115,80337,310],{"emptyLinePlaceholder":309},[115,80339,80340,80343,80345],{"class":117,"line":175},[115,80341,80342],{"class":125},"APP_NAME",[115,80344,129],{"class":121},[115,80346,133],{"class":132},[115,80348,80349,80352,80354],{"class":117,"line":350},[115,80350,80351],{"class":125},"APP_ROOT",[115,80353,129],{"class":121},[115,80355,80356],{"class":132},"\"\u002Fsrv\u002Fmyapp\"\n",[115,80358,80359,80362,80364,80366,80369],{"class":117,"line":365},[115,80360,80361],{"class":125},"RELEASES_DIR",[115,80363,129],{"class":121},[115,80365,331],{"class":132},[115,80367,80368],{"class":125},"$APP_ROOT",[115,80370,80371],{"class":132},"\u002Freleases\"\n",[115,80373,80374,80377,80379,80381,80383],{"class":117,"line":380},[115,80375,80376],{"class":125},"CURRENT_LINK",[115,80378,129],{"class":121},[115,80380,331],{"class":132},[115,80382,80368],{"class":125},[115,80384,80385],{"class":132},"\u002Fcurrent\"\n",[115,80387,80388,80391,80393,80395,80397],{"class":117,"line":487},[115,80389,80390],{"class":125},"SHARED_DIR",[115,80392,129],{"class":121},[115,80394,331],{"class":132},[115,80396,80368],{"class":125},[115,80398,80399],{"class":132},"\u002Fshared\"\n",[115,80401,80402,80405,80407,80409,80411],{"class":117,"line":2095},[115,80403,80404],{"class":125},"VENV_PATH",[115,80406,129],{"class":121},[115,80408,331],{"class":132},[115,80410,80368],{"class":125},[115,80412,80413],{"class":132},"\u002Fvenv\"\n",[115,80415,80416,80419,80421],{"class":117,"line":2104},[115,80417,80418],{"class":125},"SERVICE_NAME",[115,80420,129],{"class":121},[115,80422,80423],{"class":132},"\"gunicorn-myapp\"\n",[115,80425,80426,80429,80431],{"class":117,"line":2113},[115,80427,80428],{"class":125},"REPO_URL",[115,80430,129],{"class":121},[115,80432,80433],{"class":132},"\"git@github.com:example\u002Fmyapp.git\"\n",[115,80435,80436,80439,80441],{"class":117,"line":2122},[115,80437,80438],{"class":125},"SETTINGS_MODULE",[115,80440,129],{"class":121},[115,80442,80443],{"class":132},"\"myapp.settings.production\"\n",[115,80445,80446,80449,80451,80453,80456,80458,80460],{"class":117,"line":2131},[115,80447,80448],{"class":125},"TARGET_REVISION",[115,80450,129],{"class":121},[115,80452,331],{"class":132},[115,80454,80455],{"class":202},"${1",[115,80457,66903],{"class":121},[115,80459,65904],{"class":202},[115,80461,391],{"class":132},[115,80463,80464],{"class":117,"line":2136},[115,80465,310],{"emptyLinePlaceholder":309},[115,80467,80468,80470,80473,80476,80478,80481,80483,80486],{"class":117,"line":2142},[115,80469,10833],{"class":121},[115,80471,80472],{"class":125}," [[ ",[115,80474,80475],{"class":121},"-z",[115,80477,325],{"class":132},[115,80479,80480],{"class":125},"$TARGET_REVISION",[115,80482,331],{"class":132},[115,80484,80485],{"class":125}," ]]; ",[115,80487,66884],{"class":121},[115,80489,80490,80492,80495,80498],{"class":117,"line":2273},[115,80491,66889],{"class":202},[115,80493,80494],{"class":132}," \"Usage: ",[115,80496,80497],{"class":202},"$0",[115,80499,80500],{"class":132}," \u003Cgit-commit-or-tag>\"\n",[115,80502,80503,80506],{"class":117,"line":2282},[115,80504,80505],{"class":202},"  exit",[115,80507,8995],{"class":202},[115,80509,80510],{"class":117,"line":2291},[115,80511,66962],{"class":121},[115,80513,80514],{"class":117,"line":2299},[115,80515,310],{"emptyLinePlaceholder":309},[115,80517,80518,80520,80523,80525,80527,80529,80531,80533,80536,80538,80540,80543,80545,80547,80550,80552,80554],{"class":117,"line":2307},[115,80519,18256],{"class":121},[115,80521,80522],{"class":125}," path ",[115,80524,18262],{"class":121},[115,80526,325],{"class":132},[115,80528,80368],{"class":125},[115,80530,331],{"class":132},[115,80532,325],{"class":132},[115,80534,80535],{"class":125},"$RELEASES_DIR",[115,80537,331],{"class":132},[115,80539,325],{"class":132},[115,80541,80542],{"class":125},"$SHARED_DIR",[115,80544,331],{"class":132},[115,80546,325],{"class":132},[115,80548,80549],{"class":125},"$VENV_PATH",[115,80551,331],{"class":132},[115,80553,66942],{"class":125},[115,80555,66945],{"class":121},[115,80557,80558,80561,80564,80566,80569,80571,80574,80577,80580,80582,80585,80587,80589,80591,80594,80597],{"class":117,"line":2315},[115,80559,80560],{"class":125},"  [[ ",[115,80562,80563],{"class":121},"-e",[115,80565,325],{"class":132},[115,80567,80568],{"class":125},"$path",[115,80570,331],{"class":132},[115,80572,80573],{"class":125}," ]] ",[115,80575,80576],{"class":121},"||",[115,80578,80579],{"class":125}," { ",[115,80581,1085],{"class":202},[115,80583,80584],{"class":132}," \"Missing required path: ",[115,80586,80568],{"class":125},[115,80588,331],{"class":132},[115,80590,66942],{"class":125},[115,80592,80593],{"class":202},"exit",[115,80595,80596],{"class":202}," 1",[115,80598,80599],{"class":125},"; }\n",[115,80601,80602],{"class":117,"line":2320},[115,80603,80604],{"class":121},"done\n",[115,80606,80607],{"class":117,"line":7083},[115,80608,310],{"emptyLinePlaceholder":309},[115,80610,80611,80614,80617,80619,80621,80624,80626,80628,80630,80632,80635,80637,80639,80641,80643,80645],{"class":117,"line":7090},[115,80612,80613],{"class":125},"[[ ",[115,80615,80616],{"class":121},"-f",[115,80618,325],{"class":132},[115,80620,80542],{"class":125},[115,80622,80623],{"class":132},"\u002F.env\"",[115,80625,80573],{"class":125},[115,80627,80576],{"class":121},[115,80629,80579],{"class":125},[115,80631,1085],{"class":202},[115,80633,80634],{"class":132}," \"Missing env file: ",[115,80636,80542],{"class":125},[115,80638,80623],{"class":132},[115,80640,66942],{"class":125},[115,80642,80593],{"class":202},[115,80644,80596],{"class":202},[115,80646,80599],{"class":125},[115,80648,80649],{"class":117,"line":7097},[115,80650,310],{"emptyLinePlaceholder":309},[115,80652,80653,80655,80658,80660,80662],{"class":117,"line":7108},[115,80654,67026],{"class":202},[115,80656,80657],{"class":121}," 9>",[115,80659,331],{"class":132},[115,80661,80368],{"class":125},[115,80663,80664],{"class":132},"\u002Fdeploy.lock\"\n",[115,80666,80667,80670,80672,80675,80677,80679,80681,80684,80686,80688,80690],{"class":117,"line":7113},[115,80668,80669],{"class":262},"flock",[115,80671,2794],{"class":202},[115,80673,80674],{"class":202}," 9",[115,80676,43235],{"class":121},[115,80678,80579],{"class":125},[115,80680,1085],{"class":202},[115,80682,80683],{"class":132}," \"Another deployment is already running\"",[115,80685,66942],{"class":125},[115,80687,80593],{"class":202},[115,80689,80596],{"class":202},[115,80691,80599],{"class":125},[115,80693,80694],{"class":117,"line":16535},[115,80695,310],{"emptyLinePlaceholder":309},[115,80697,80698,80701,80703,80706,80708],{"class":117,"line":16544},[115,80699,80700],{"class":125},"TIMESTAMP",[115,80702,129],{"class":121},[115,80704,80705],{"class":132},"\"$(",[115,80707,301],{"class":262},[115,80709,80710],{"class":132}," +%Y%m%d-%H%M%S)\"\n",[115,80712,80713,80716,80718,80720,80722,80724,80727],{"class":117,"line":16549},[115,80714,80715],{"class":125},"NEW_RELEASE",[115,80717,129],{"class":121},[115,80719,331],{"class":132},[115,80721,80535],{"class":125},[115,80723,44219],{"class":132},[115,80725,80726],{"class":125},"$TIMESTAMP",[115,80728,391],{"class":132},[115,80730,80731,80734,80736,80738,80740,80743,80745],{"class":117,"line":16555},[115,80732,80733],{"class":125},"LOG_FILE",[115,80735,129],{"class":121},[115,80737,331],{"class":132},[115,80739,80542],{"class":125},[115,80741,80742],{"class":132},"\u002Flogs\u002Fdeploy-",[115,80744,80726],{"class":125},[115,80746,80747],{"class":132},".log\"\n",[115,80749,80750,80753,80755,80757,80759,80761,80763,80766,80769,80771,80774],{"class":117,"line":16564},[115,80751,80752],{"class":125},"PREVIOUS_RELEASE",[115,80754,129],{"class":121},[115,80756,80705],{"class":132},[115,80758,31686],{"class":262},[115,80760,2777],{"class":202},[115,80762,325],{"class":132},[115,80764,80765],{"class":125},"$CURRENT_LINK",[115,80767,80768],{"class":132},"\" ",[115,80770,80576],{"class":121},[115,80772,80773],{"class":202}," true",[115,80775,80776],{"class":132},")\"\n",[115,80778,80779],{"class":117,"line":16573},[115,80780,310],{"emptyLinePlaceholder":309},[115,80782,80783,80785,80787,80790,80793,80795,80797,80800,80803],{"class":117,"line":16582},[115,80784,67026],{"class":202},[115,80786,604],{"class":121},[115,80788,80789],{"class":132}," >(",[115,80791,80792],{"class":262},"tee",[115,80794,8584],{"class":202},[115,80796,325],{"class":132},[115,80798,80799],{"class":125},"$LOG_FILE",[115,80801,80802],{"class":132},"\")",[115,80804,80805],{"class":121}," 2>&1\n",[115,80807,80808],{"class":117,"line":16587},[115,80809,310],{"emptyLinePlaceholder":309},[115,80811,80812,80814],{"class":117,"line":16596},[115,80813,41151],{"class":262},[115,80815,80816],{"class":125},"() {\n",[115,80818,80819,80821],{"class":117,"line":16609},[115,80820,66889],{"class":202},[115,80822,80823],{"class":132}," \"Deployment failed\"\n",[115,80825,80826,80829,80831,80833,80835,80837,80839,80841,80844,80846,80848,80850,80852,80854,80856],{"class":117,"line":16614},[115,80827,80828],{"class":121},"  if",[115,80830,80472],{"class":125},[115,80832,66871],{"class":121},[115,80834,19856],{"class":132},[115,80836,80752],{"class":125},[115,80838,66903],{"class":121},[115,80840,66939],{"class":132},[115,80842,80843],{"class":121}," &&",[115,80845,1019],{"class":121},[115,80847,19856],{"class":132},[115,80849,80752],{"class":125},[115,80851,66903],{"class":121},[115,80853,66939],{"class":132},[115,80855,80485],{"class":125},[115,80857,66884],{"class":121},[115,80859,80860,80863,80866,80869],{"class":117,"line":16624},[115,80861,80862],{"class":202},"    echo",[115,80864,80865],{"class":132}," \"Rolling back to ",[115,80867,80868],{"class":125},"$PREVIOUS_RELEASE",[115,80870,391],{"class":132},[115,80872,80873,80876,80878,80880,80882,80884,80886,80888],{"class":117,"line":16632},[115,80874,80875],{"class":262},"    ln",[115,80877,14857],{"class":202},[115,80879,325],{"class":132},[115,80881,80868],{"class":125},[115,80883,331],{"class":132},[115,80885,325],{"class":132},[115,80887,80765],{"class":125},[115,80889,391],{"class":132},[115,80891,80892,80895,80897,80899,80901,80904],{"class":117,"line":16640},[115,80893,80894],{"class":262},"    sudo",[115,80896,3480],{"class":132},[115,80898,3483],{"class":132},[115,80900,325],{"class":132},[115,80902,80903],{"class":125},"$SERVICE_NAME",[115,80905,391],{"class":132},[115,80907,80908],{"class":117,"line":16646},[115,80909,80910],{"class":121},"  fi\n",[115,80912,80913],{"class":117,"line":16651},[115,80914,2323],{"class":125},[115,80916,80917],{"class":117,"line":16656},[115,80918,310],{"emptyLinePlaceholder":309},[115,80920,80921,80924,80927],{"class":117,"line":16666},[115,80922,80923],{"class":202},"trap",[115,80925,80926],{"class":132}," rollback",[115,80928,80929],{"class":132}," ERR\n",[115,80931,80932],{"class":117,"line":16674},[115,80933,310],{"emptyLinePlaceholder":309},[115,80935,80936,80938,80941,80943],{"class":117,"line":16687},[115,80937,1085],{"class":202},[115,80939,80940],{"class":132}," \"Starting deployment at ",[115,80942,80726],{"class":125},[115,80944,391],{"class":132},[115,80946,80947,80949,80952,80954],{"class":117,"line":16692},[115,80948,1085],{"class":202},[115,80950,80951],{"class":132}," \"Target revision: ",[115,80953,80480],{"class":125},[115,80955,391],{"class":132},[115,80957,80958],{"class":117,"line":16697},[115,80959,310],{"emptyLinePlaceholder":309},[115,80961,80962,80964,80966,80968,80971,80973,80975,80978],{"class":117,"line":16702},[115,80963,13525],{"class":262},[115,80965,14843],{"class":132},[115,80967,325],{"class":132},[115,80969,80970],{"class":125},"$REPO_URL",[115,80972,331],{"class":132},[115,80974,325],{"class":132},[115,80976,80977],{"class":125},"$NEW_RELEASE",[115,80979,391],{"class":132},[115,80981,80982,80984,80986,80988],{"class":117,"line":16711},[115,80983,5303],{"class":202},[115,80985,325],{"class":132},[115,80987,80977],{"class":125},[115,80989,391],{"class":132},[115,80991,80992,80994,80997,81000],{"class":117,"line":16719},[115,80993,13525],{"class":262},[115,80995,80996],{"class":132}," fetch",[115,80998,80999],{"class":202}," --tags",[115,81001,81002],{"class":132}," origin\n",[115,81004,81005,81007,81009,81012,81014,81016],{"class":117,"line":16732},[115,81006,13525],{"class":262},[115,81008,30452],{"class":132},[115,81010,81011],{"class":202}," --detach",[115,81013,325],{"class":132},[115,81015,80480],{"class":125},[115,81017,391],{"class":132},[115,81019,81020],{"class":117,"line":16745},[115,81021,310],{"emptyLinePlaceholder":309},[115,81023,81024,81026,81028,81030,81032,81035,81037,81039],{"class":117,"line":16751},[115,81025,14854],{"class":262},[115,81027,14857],{"class":202},[115,81029,325],{"class":132},[115,81031,80542],{"class":125},[115,81033,81034],{"class":132},"\u002Fmedia\"",[115,81036,325],{"class":132},[115,81038,80977],{"class":125},[115,81040,81041],{"class":132},"\u002Fmedia\"\n",[115,81043,81044],{"class":117,"line":28158},[115,81045,310],{"emptyLinePlaceholder":309},[115,81047,81048,81050,81052,81054],{"class":117,"line":28164},[115,81049,5311],{"class":202},[115,81051,325],{"class":132},[115,81053,80549],{"class":125},[115,81055,81056],{"class":132},"\u002Fbin\u002Factivate\"\n",[115,81058,81059,81061,81063,81065,81067,81070],{"class":117,"line":28170},[115,81060,122],{"class":121},[115,81062,77257],{"class":125},[115,81064,129],{"class":121},[115,81066,331],{"class":132},[115,81068,81069],{"class":125},"$SETTINGS_MODULE",[115,81071,391],{"class":132},[115,81073,81074],{"class":117,"line":28175},[115,81075,310],{"emptyLinePlaceholder":309},[115,81077,81078,81080],{"class":117,"line":28187},[115,81079,203],{"class":202},[115,81081,206],{"class":202},[115,81083,81084,81086,81088,81090],{"class":117,"line":28196},[115,81085,5311],{"class":202},[115,81087,325],{"class":132},[115,81089,80542],{"class":125},[115,81091,81092],{"class":132},"\u002F.env\"\n",[115,81094,81095,81097],{"class":117,"line":28202},[115,81096,203],{"class":202},[115,81098,221],{"class":132},[115,81100,81101],{"class":117,"line":28208},[115,81102,310],{"emptyLinePlaceholder":309},[115,81104,81105,81107,81109,81111,81114,81116,81119,81122],{"class":117,"line":28214},[115,81106,241],{"class":202},[115,81108,19856],{"class":132},[115,81110,34901],{"class":125},[115,81112,81113],{"class":121},":?",[115,81115,34901],{"class":125},[115,81117,81118],{"class":125}," is",[115,81120,81121],{"class":125}," required",[115,81123,81124],{"class":132},"}\"\n",[115,81126,81127,81129,81131,81133,81135,81137,81139,81141],{"class":117,"line":28220},[115,81128,241],{"class":202},[115,81130,19856],{"class":132},[115,81132,10873],{"class":125},[115,81134,81113],{"class":121},[115,81136,10873],{"class":125},[115,81138,81118],{"class":125},[115,81140,81121],{"class":125},[115,81142,81124],{"class":132},[115,81144,81145],{"class":117,"line":28226},[115,81146,310],{"emptyLinePlaceholder":309},[115,81148,81149,81151,81153,81155],{"class":117,"line":28232},[115,81150,8618],{"class":262},[115,81152,6600],{"class":132},[115,81154,12350],{"class":202},[115,81156,12353],{"class":132},[115,81158,81159],{"class":117,"line":28237},[115,81160,310],{"emptyLinePlaceholder":309},[115,81162,81163,81165,81167,81169],{"class":117,"line":28243},[115,81164,1114],{"class":262},[115,81166,1117],{"class":132},[115,81168,1814],{"class":132},[115,81170,1817],{"class":202},[115,81172,81173,81175,81177,81179],{"class":117,"line":28249},[115,81174,1114],{"class":262},[115,81176,1117],{"class":132},[115,81178,1826],{"class":132},[115,81180,1841],{"class":202},[115,81182,81183,81185,81187,81189],{"class":117,"line":28255},[115,81184,1114],{"class":262},[115,81186,1117],{"class":132},[115,81188,1838],{"class":132},[115,81190,1841],{"class":202},[115,81192,81193],{"class":117,"line":28260},[115,81194,310],{"emptyLinePlaceholder":309},[115,81196,81197,81199,81201,81203,81205,81207,81209,81211],{"class":117,"line":28266},[115,81198,14854],{"class":262},[115,81200,14857],{"class":202},[115,81202,325],{"class":132},[115,81204,80977],{"class":125},[115,81206,331],{"class":132},[115,81208,325],{"class":132},[115,81210,80765],{"class":125},[115,81212,391],{"class":132},[115,81214,81215],{"class":117,"line":28272},[115,81216,310],{"emptyLinePlaceholder":309},[115,81218,81219,81221,81223,81225,81227,81229,81231,81233,81235,81237,81239,81241,81243],{"class":117,"line":28277},[115,81220,2001],{"class":262},[115,81222,3480],{"class":132},[115,81224,3919],{"class":132},[115,81226,325],{"class":132},[115,81228,80903],{"class":125},[115,81230,331],{"class":132},[115,81232,43235],{"class":121},[115,81234,14228],{"class":262},[115,81236,3480],{"class":132},[115,81238,3483],{"class":132},[115,81240,325],{"class":132},[115,81242,80903],{"class":125},[115,81244,391],{"class":132},[115,81246,81247,81249,81251,81253,81255,81257,81259],{"class":117,"line":28283},[115,81248,2001],{"class":262},[115,81250,3480],{"class":132},[115,81252,1984],{"class":132},[115,81254,325],{"class":132},[115,81256,80903],{"class":125},[115,81258,331],{"class":132},[115,81260,2800],{"class":202},[115,81262,81263],{"class":117,"line":28289},[115,81264,310],{"emptyLinePlaceholder":309},[115,81266,81267,81269,81271,81273,81276,81279,81282,81285,81287,81290,81292],{"class":117,"line":28295},[115,81268,2764],{"class":262},[115,81270,28554],{"class":202},[115,81272,28557],{"class":202},[115,81274,81275],{"class":202}," --show-error",[115,81277,81278],{"class":202}," --max-time",[115,81280,81281],{"class":202}," 10",[115,81283,81284],{"class":202}," --retry",[115,81286,69997],{"class":202},[115,81288,81289],{"class":202}," --retry-delay",[115,81291,43908],{"class":202},[115,81293,13426],{"class":132},[115,81295,81296],{"class":117,"line":28301},[115,81297,310],{"emptyLinePlaceholder":309},[115,81299,81301,81303,81306,81308],{"class":117,"line":81300},81,[115,81302,1085],{"class":202},[115,81304,81305],{"class":132}," \"Deployment successful: ",[115,81307,80977],{"class":125},[115,81309,391],{"class":132},[115,81311,81313,81315,81318,81320,81322,81325],{"class":117,"line":81312},82,[115,81314,1085],{"class":202},[115,81316,81317],{"class":132}," \"Previous release was: ${",[115,81319,80752],{"class":125},[115,81321,66903],{"class":121},[115,81323,81324],{"class":125},"none",[115,81326,81124],{"class":132},[115,81328,81330],{"class":117,"line":81329},83,[115,81331,310],{"emptyLinePlaceholder":309},[115,81333,81335,81337,81339],{"class":117,"line":81334},84,[115,81336,80923],{"class":202},[115,81338,4009],{"class":132},[115,81340,80929],{"class":132},[16,81342,81343],{},"Make it executable:",[106,81345,81347],{"className":108,"code":81346,"language":110,"meta":111,"style":111},"chmod 750 deploy.sh\n",[20,81348,81349],{"__ignoreMap":111},[115,81350,81351,81353,81355],{"class":117,"line":118},[115,81352,263],{"class":262},[115,81354,36899],{"class":202},[115,81356,81357],{"class":132}," deploy.sh\n",[16,81359,81360],{},"Run it with a pinned revision:",[106,81362,81364],{"className":108,"code":81363,"language":110,"meta":111,"style":111},".\u002Fdeploy.sh v2026.04.24\n",[20,81365,81366],{"__ignoreMap":111},[115,81367,81368,81371],{"class":117,"line":118},[115,81369,81370],{"class":262},".\u002Fdeploy.sh",[115,81372,81373],{"class":132}," v2026.04.24\n",[16,81375,46006],{},[106,81377,81379],{"className":108,"code":81378,"language":110,"meta":111,"style":111},".\u002Fdeploy.sh 8f3c2d1\n",[20,81380,81381],{"__ignoreMap":111},[115,81382,81383,81385],{"class":117,"line":118},[115,81384,81370],{"class":262},[115,81386,81387],{"class":132}," 8f3c2d1\n",[16,81389,12356,81390,81392,81393,81395],{},[20,81391,11918],{}," points to the intended production path. In release-directory deploys, ",[20,81394,11918],{}," is often a shared path or another persistent location managed outside each individual release.",[11,81397,81399],{"id":81398},"add-production-safety-checks-to-the-script","Add production safety checks to the script",[52,81401,81403,81404],{"id":81402},"fail-fast-with-set-euo-pipefail","Fail fast with ",[20,81405,79931],{},[16,81407,81408],{},"This avoids silent failures:",[63,81410,81411,81416,81422],{},[66,81412,81413,81415],{},[20,81414,80563],{},": exit on command failure",[66,81417,81418,81421],{},[20,81419,81420],{},"-u",": fail on unset variables",[66,81423,81424,81427],{},[20,81425,81426],{},"pipefail",": fail if any command in a pipeline fails",[52,81429,81431],{"id":81430},"confirm-required-environment-variables-exist","Confirm required environment variables exist",[16,81433,81434,81435,241],{},"Validate the variables your Django settings actually require after loading ",[20,81436,191],{},[106,81438,81440],{"className":108,"code":81439,"language":110,"meta":111,"style":111},": \"${DJANGO_SECRET_KEY:?DJANGO_SECRET_KEY is required}\"\n: \"${DATABASE_URL:?DATABASE_URL is required}\"\n",[20,81441,81442,81460],{"__ignoreMap":111},[115,81443,81444,81446,81448,81450,81452,81454,81456,81458],{"class":117,"line":118},[115,81445,241],{"class":202},[115,81447,19856],{"class":132},[115,81449,34901],{"class":125},[115,81451,81113],{"class":121},[115,81453,34901],{"class":125},[115,81455,81118],{"class":125},[115,81457,81121],{"class":125},[115,81459,81124],{"class":132},[115,81461,81462,81464,81466,81468,81470,81472,81474,81476],{"class":117,"line":136},[115,81463,241],{"class":202},[115,81465,19856],{"class":132},[115,81467,10873],{"class":125},[115,81469,81113],{"class":121},[115,81471,10873],{"class":125},[115,81473,81118],{"class":125},[115,81475,81121],{"class":125},[115,81477,81124],{"class":132},[16,81479,81480],{},"If your project uses different names, replace those examples with your real variable names. The important part is to fail before migrations or service restarts if settings cannot load correctly.",[52,81482,81484],{"id":81483},"prevent-accidental-deploys-to-the-wrong-branch","Prevent accidental deploys to the wrong branch",[16,81486,81487,81488,81491],{},"Deploy a tag or commit hash, not “whatever is on main right now”. ",[20,81489,81490],{},"git checkout --detach \"$TARGET_REVISION\""," makes the release explicit and auditable.",[52,81493,81495],{"id":81494},"use-a-lock-file-to-avoid-concurrent-deployments","Use a lock file to avoid concurrent deployments",[16,81497,12998,81498,81500],{},[20,81499,80669],{}," pattern prevents two deploy processes from overlapping and racing on migrations, symlink switches, or service reloads.",[52,81502,81504],{"id":81503},"log-output-for-auditing-and-debugging","Log output for auditing and debugging",[16,81506,12998,81507,81509,81510,81513],{},[20,81508,80792],{}," log pattern keeps a dated deploy log in ",[20,81511,81512],{},"\u002Fsrv\u002Fmyapp\u002Fshared\u002Flogs\u002F",", which helps when a health check fails after restart.",[11,81515,81517],{"id":81516},"handle-migrations-safely-in-a-deployment-script","Handle migrations safely in a deployment script",[16,81519,81520],{},"Not all migrations are equally safe. Adding a nullable column is usually lower risk than dropping columns or rewriting large tables.",[16,81522,81523],{},"Run this before restart:",[106,81525,81527],{"className":108,"code":81526,"language":110,"meta":111,"style":111},"python manage.py check --deploy\npython manage.py migrate --noinput\n",[20,81528,81529,81539],{"__ignoreMap":111},[115,81530,81531,81533,81535,81537],{"class":117,"line":118},[115,81532,1114],{"class":262},[115,81534,1117],{"class":132},[115,81536,1814],{"class":132},[115,81538,1817],{"class":202},[115,81540,81541,81543,81545,81547],{"class":117,"line":136},[115,81542,1114],{"class":262},[115,81544,1117],{"class":132},[115,81546,1826],{"class":132},[115,81548,1841],{"class":202},[16,81550,81551,81553],{},[20,81552,15970],{}," is only meaningful when your real production settings are loaded correctly. That includes at least:",[63,81555,81556,81560,81564,81568,81571],{},[66,81557,81558],{},[20,81559,32431],{},[66,81561,81562],{},[20,81563,2719],{},[66,81565,81566,73544],{},[20,81567,2725],{},[66,81569,81570],{},"secure cookie and SSL-related settings",[66,81572,81573,81574,81576],{},"proxy settings such as ",[20,81575,2377],{}," when HTTPS is terminated by Nginx, Caddy, or a load balancer",[16,81578,81579],{},"If a migration can lock large tables or requires app code and schema changes to happen in stages, do not treat it as a routine deploy step. Plan a maintenance window, pre-deploy backup or checkpoint, or use a backward-compatible migration strategy.",[16,81581,81582,81583,211],{},"For deeper migration guidance, see ",[1395,81584,1410],{"href":1409},[11,81586,81588],{"id":81587},"add-rollback-handling","Add rollback handling",[16,81590,81591],{},"If the new release fails after the symlink switch, production should not stay pointed at a bad release. That is why the example script records the previous release and uses a trap to restore it on failure.",[16,81593,81594],{},"The manual rollback path is still useful:",[106,81596,81598],{"className":108,"code":81597,"language":110,"meta":111,"style":111},"ln -sfn \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260423-101500 \u002Fsrv\u002Fmyapp\u002Fcurrent\nsudo systemctl reload gunicorn-myapp || sudo systemctl restart gunicorn-myapp\ncurl --fail --silent --show-error --max-time 10 --retry 5 --retry-delay 2 https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,81599,81600,81611,81631],{"__ignoreMap":111},[115,81601,81602,81604,81606,81609],{"class":117,"line":118},[115,81603,14854],{"class":262},[115,81605,14857],{"class":202},[115,81607,81608],{"class":132}," \u002Fsrv\u002Fmyapp\u002Freleases\u002F20260423-101500",[115,81610,5306],{"class":132},[115,81612,81613,81615,81617,81619,81621,81623,81625,81627,81629],{"class":117,"line":136},[115,81614,2001],{"class":262},[115,81616,3480],{"class":132},[115,81618,3919],{"class":132},[115,81620,15518],{"class":132},[115,81622,43235],{"class":121},[115,81624,14228],{"class":262},[115,81626,3480],{"class":132},[115,81628,3483],{"class":132},[115,81630,15480],{"class":132},[115,81632,81633,81635,81637,81639,81641,81643,81645,81647,81649,81651,81653],{"class":117,"line":149},[115,81634,2764],{"class":262},[115,81636,28554],{"class":202},[115,81638,28557],{"class":202},[115,81640,81275],{"class":202},[115,81642,81278],{"class":202},[115,81644,81281],{"class":202},[115,81646,81284],{"class":202},[115,81648,69997],{"class":202},[115,81650,81289],{"class":202},[115,81652,43908],{"class":202},[115,81654,13426],{"class":132},[16,81656,81657],{},"Be careful if migrations already ran. Code rollback is usually easier than schema rollback. If the new code depends on irreversible schema changes, your rollback plan must account for that before the deploy starts.",[16,81659,81660,81661,211],{},"For a fuller recovery path, see ",[1395,81662,81663],{"href":1415},"How to Roll Back a Django Deployment",[11,81665,81667],{"id":81666},"integrate-the-script-with-systemd-and-your-app-server","Integrate the script with systemd and your app server",[16,81669,81670],{},"For Gunicorn, prefer reload when your service unit and app behavior support it cleanly:",[106,81672,81674],{"className":108,"code":81673,"language":110,"meta":111,"style":111},"sudo systemctl reload gunicorn-myapp\n",[20,81675,81676],{"__ignoreMap":111},[115,81677,81678,81680,81682,81684],{"class":117,"line":118},[115,81679,2001],{"class":262},[115,81681,3480],{"class":132},[115,81683,3919],{"class":132},[115,81685,15480],{"class":132},[16,81687,81688],{},"If reload is not configured or does not apply changes reliably, use:",[106,81690,81692],{"className":108,"code":81691,"language":110,"meta":111,"style":111},"sudo systemctl restart gunicorn-myapp\n",[20,81693,81694],{"__ignoreMap":111},[115,81695,81696,81698,81700,81702],{"class":117,"line":118},[115,81697,2001],{"class":262},[115,81699,3480],{"class":132},[115,81701,3483],{"class":132},[115,81703,15480],{"class":132},[16,81705,81706,81707,81709],{},"For Uvicorn under ",[20,81708,1277],{},", the same pattern applies, but verify your service unit and process model first.",[16,81711,81712,81713,81715,81716,81718,81719,81722],{},"This script assumes the deploy user has the required ",[20,81714,2001],{}," permissions to manage the app service. If not, configure a controlled ",[20,81717,27403],{}," rule or use a wrapper service account. Do not assume ",[20,81720,81721],{},"sudo systemctl"," will work on every server without that setup.",[16,81724,81725],{},"Run the script:",[63,81727,81728,81731,81734],{},[66,81729,81730],{},"manually over SSH",[66,81732,81733],{},"from a CI job that SSHes into the server",[66,81735,81736],{},"from a controlled release runner",[16,81738,81739],{},"Do not trigger production deploys from cron unless you are solving a specific operational problem and have review controls elsewhere.",[11,81741,22690],{"id":22689},[16,81743,81744],{},"After every deploy, verify all of these:",[106,81746,81748],{"className":108,"code":81747,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn-myapp --no-pager\ncurl --fail --silent --show-error --max-time 10 --retry 5 --retry-delay 2 https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,81749,81750,81762],{"__ignoreMap":111},[115,81751,81752,81754,81756,81758,81760],{"class":117,"line":118},[115,81753,2001],{"class":262},[115,81755,3480],{"class":132},[115,81757,1984],{"class":132},[115,81759,15518],{"class":132},[115,81761,2800],{"class":202},[115,81763,81764,81766,81768,81770,81772,81774,81776,81778,81780,81782,81784],{"class":117,"line":136},[115,81765,2764],{"class":262},[115,81767,28554],{"class":202},[115,81769,28557],{"class":202},[115,81771,81275],{"class":202},[115,81773,81278],{"class":202},[115,81775,81281],{"class":202},[115,81777,81284],{"class":202},[115,81779,69997],{"class":202},[115,81781,81289],{"class":202},[115,81783,43908],{"class":202},[115,81785,13426],{"class":132},[16,81787,81788],{},"If possible, also test an internal or localhost health endpoint through the actual reverse proxy path used in production. That avoids depending only on external DNS or edge routing during deployment verification.",[16,81790,33323],{},[63,81792,81793,81796,81799,81802,81805,81808],{},[66,81794,81795],{},"the site loads through Nginx or Caddy",[66,81797,81798],{},"admin login works if applicable",[66,81800,81801],{},"new static assets are present",[66,81803,81804],{},"migrations were applied to the intended database",[66,81806,81807],{},"HTTPS and proxy-aware settings behave correctly behind your reverse proxy",[66,81809,81810],{},"Celery workers were restarted if the release changed task code",[16,81812,81813],{},"If the release changes task code, include explicit worker restarts in the deploy flow so web and worker code stay in sync:",[106,81815,81817],{"className":108,"code":81816,"language":110,"meta":111,"style":111},"sudo systemctl restart celery-myapp\nsudo systemctl restart celerybeat-myapp\n",[20,81818,81819,81830],{"__ignoreMap":111},[115,81820,81821,81823,81825,81827],{"class":117,"line":118},[115,81822,2001],{"class":262},[115,81824,3480],{"class":132},[115,81826,3483],{"class":132},[115,81828,81829],{"class":132}," celery-myapp\n",[115,81831,81832,81834,81836,81838],{"class":117,"line":136},[115,81833,2001],{"class":262},[115,81835,3480],{"class":132},[115,81837,3483],{"class":132},[115,81839,81840],{"class":132}," celerybeat-myapp\n",[11,81842,1321],{"id":1320},[16,81844,81845],{},"This setup works because it separates deployment from provisioning and makes each release explicit. You deploy a known revision, validate the environment, run Django’s built-in deployment checks, apply schema changes, update static assets, and only then switch traffic to the new code.",[16,81847,81848],{},"A release-directory layout gives you a cleaner rollback path than an in-place Git checkout. It also reduces the chance of partially updated code being served during deployment. In-place deploys are simpler, but release directories are usually the better production default.",[16,81850,81851],{},"The main production risks are not in the Bash syntax itself. They are in the release order:",[63,81853,81854,81857,81860,81866,81869,81872],{},[66,81855,81856],{},"loading the wrong settings",[66,81858,81859],{},"running migrations against the wrong database",[66,81861,81862,81863,81865],{},"switching ",[20,81864,13654],{}," too early",[66,81867,81868],{},"failing health checks without rollback",[66,81870,81871],{},"forgetting dependent processes like Celery",[66,81873,81874],{},"assuming static and media files live inside each release",[16,81876,81877],{},"When this process becomes repetitive across multiple projects, the first things worth standardizing are the safe Bash header, environment validation block, release directory layout, systemd restart logic, health checks, and rollback section. Those are good candidates for a reusable internal script template if you manage several Django apps with the same deployment pattern.",[11,81879,30532],{"id":30531},[63,81881,81882,81888,81893,81899,81906,81916,81926,81932],{},[66,81883,81884,81887],{},[1226,81885,81886],{},"Celery",": restart workers after deploy if task code changed. Keep worker and web releases aligned.",[66,81889,81890,81892],{},[1226,81891,35736],{},": if you deploy containers, the Bash script should usually build or pull an image, run migrations in a controlled step, and restart containers through Compose or your orchestrator instead of activating a host virtualenv.",[66,81894,81895,81898],{},[1226,81896,81897],{},"Zero downtime",": a Bash script alone does not guarantee zero downtime. Migrations, process reload behavior, connection draining, and proxy configuration all affect whether requests fail during release.",[66,81900,81901,2513,81903,81905],{},[1226,81902,70969],{},[20,81904,13689],{}," manages static assets, not user-uploaded media. Media should live in a shared path or object storage, not inside release directories.",[66,81907,81908,81911,81912,81915],{},[1226,81909,81910],{},"Shared env files",": if you use ",[20,81913,81914],{},"source \"$SHARED_DIR\u002F.env\"",", keep that file admin-controlled and shell-safe.",[66,81917,81918,81921,81922,81925],{},[1226,81919,81920],{},"Destructive commands",": validate paths before using commands like ",[20,81923,81924],{},"rm -rf",". Never build delete paths from unchecked variables.",[66,81927,81928,81931],{},[1226,81929,81930],{},"Multi-server deploys",": coordinate migrations and app restarts carefully. On multiple app servers, deploy code in a controlled order and avoid incompatible code\u002Fschema transitions.",[66,81933,81934,81936],{},[1226,81935,17886],{},": a previous code release is not always safe against a new schema. Test rollback on real staging data patterns, not only on clean local databases.",[11,81938,1386],{"id":1385},[63,81940,81941,81947,81952,81956,81961],{},[66,81942,81943,81944,81946],{},"Start with the ",[1395,81945,3000],{"href":2999}," if you need the full production sequence.",[66,81948,81949,81950,211],{},"If you still need to set up the runtime stack, read ",[1395,81951,2986],{"href":2985},[66,81953,8035,81954,211],{},[1395,81955,8039],{"href":8038},[66,81957,81958,81959,211],{},"For migration-specific risk management, read ",[1395,81960,1410],{"href":1409},[66,81962,81963,81964,211],{},"For recovery planning, read ",[1395,81965,81663],{"href":1415},[11,81967,1420],{"id":1419},[52,81969,81971],{"id":81970},"should-a-django-deployment-script-run-migrations-automatically","Should a Django deployment script run migrations automatically?",[16,81973,81974],{},"Yes, if migrations are part of your normal release path and you understand their risk. For large or potentially blocking schema changes, use a staged migration plan or maintenance window instead of treating them as routine.",[52,81976,81978],{"id":81977},"where-should-the-script-live-on-the-server-or-in-the-repo","Where should the script live: on the server or in the repo?",[16,81980,81981],{},"Usually in the repo, because it versions the release logic with the app. On-server wrapper scripts are still useful for environment-specific paths, permissions, or CI entrypoints.",[52,81983,81985],{"id":81984},"should-the-script-restart-gunicorn-or-reload-it","Should the script restart Gunicorn or reload it?",[16,81987,55,81988,81991,81992,81995],{},[20,81989,81990],{},"reload"," when your Gunicorn service is configured for it and you have verified it works reliably with your app. Use ",[20,81993,81994],{},"restart"," when you need the simpler and more predictable behavior.",[52,81997,81999],{"id":81998},"is-it-better-to-deploy-by-branch-name-tag-or-commit-hash","Is it better to deploy by branch name, tag, or commit hash?",[16,82001,82002],{},"Use a tag or commit hash in production. A branch name is mutable and can point to different code over time, which makes rollbacks and auditing harder.",[52,82004,82006],{"id":82005},"how-do-i-make-a-bash-deployment-script-safer-for-production","How do I make a Bash deployment script safer for production?",[16,82008,55,82009,82011,82012,82014],{},[20,82010,79931],{},", require a pinned revision, validate paths and variables, keep secrets out of the script, prevent concurrent deploys with ",[20,82013,80669],{},", log every run, verify health after restart, include worker restarts where needed, and test rollback before you need it.",[1485,82016,82017],{},"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .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}",{"title":111,"searchDepth":149,"depth":149,"links":82019},[82020,82021,82022,82023,82028,82033,82034,82042,82043,82044,82045,82046,82047,82048,82049],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":79971,"depth":136,"text":79972,"children":82024},[82025,82026,82027],{"id":79975,"depth":149,"text":79976},{"id":80089,"depth":149,"text":80090},{"id":80125,"depth":149,"text":80126},{"id":80148,"depth":136,"text":80149,"children":82029},[82030,82031,82032],{"id":80152,"depth":149,"text":80153},{"id":80189,"depth":149,"text":80190},{"id":80252,"depth":149,"text":80253},{"id":80285,"depth":136,"text":80286},{"id":81398,"depth":136,"text":81399,"children":82035},[82036,82038,82039,82040,82041],{"id":81402,"depth":149,"text":82037},"Fail fast with set -euo pipefail",{"id":81430,"depth":149,"text":81431},{"id":81483,"depth":149,"text":81484},{"id":81494,"depth":149,"text":81495},{"id":81503,"depth":149,"text":81504},{"id":81516,"depth":136,"text":81517},{"id":81587,"depth":136,"text":81588},{"id":81666,"depth":136,"text":81667},{"id":22689,"depth":136,"text":22690},{"id":1320,"depth":136,"text":1321},{"id":30531,"depth":136,"text":30532},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":82050},[82051,82052,82053,82054,82055],{"id":81970,"depth":149,"text":81971},{"id":81977,"depth":149,"text":81978},{"id":81984,"depth":149,"text":81985},{"id":81998,"depth":149,"text":81999},{"id":82005,"depth":149,"text":82006},"A manual Django deployment often looks simple at first: SSH into a server, pull code, install dependencies, run migrations, collect static files, restart Gunicorn, and hope the...",{},"\u002Fdjango-deployment-script-bash-guide",[1552,1397,37876],{"title":79877,"description":82056},[1557,110,49631],"django-deployment-script-bash-guide",[1557,110,49631],"n1gLbIEwTagvwTx6ciWzrS8UENy1Zj9Uy7QOcFOxWTo",{"id":82066,"title":82067,"body":82068,"category":1541,"description":83414,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":3091,"meta":83415,"navigation":309,"path":83416,"priority":14025,"related":83417,"role":34162,"section":1554,"seo":83418,"stack":83419,"stem":83420,"tags":83421,"type":3104,"__hash__":83422},"articles\u002Fmanual-vs-automated-django-deployment.md","Manual vs Automated Django Deployment: When to Switch",{"type":8,"value":82069,"toc":83371},[82070,82072,82075,82078,82081,82083,82086,82089,82109,82112,82129,82131,82135,82138,82307,82310,82337,82340,82354,82358,82361,82365,82397,82400,82443,82447,82450,82464,82467,82523,82530,82532,82567,82580,82584,82587,82601,82604,82607,82671,82674,82678,82681,82685,82698,82702,82716,82720,82731,82735,82738,82742,82745,82934,82937,82941,82944,82962,82965,83006,83010,83013,83027,83030,83034,83037,83041,83044,83058,83062,83065,83079,83083,83086,83097,83101,83104,83108,83112,83115,83119,83122,83126,83129,83132,83179,83183,83186,83203,83207,83233,83235,83238,83241,83244,83246,83301,83303,83308,83313,83318,83324,83331,83333,83337,83340,83344,83347,83351,83354,83358,83361,83365,83368],[11,82071,14],{"id":13},[16,82073,82074],{},"Many Django teams start with a manual deployment process over SSH. That is normal: log into a Linux server, pull code, install dependencies, run migrations, collect static files, restart Gunicorn, reload Nginx, and test the app.",[16,82076,82077],{},"The problem is that a manual Django deployment process usually works right up until it does not. As releases become more frequent, more than one person touches production, or uptime requirements increase, manual steps become a source of drift, missed commands, downtime, and slow rollback. At that point, automated Django deployment becomes less of a convenience and more of a reliability control.",[16,82079,82080],{},"The practical question is not “manual or automation forever.” It is: when is manual deployment still acceptable, and what should you automate first without overengineering the release path?",[11,82082,30],{"id":29},[16,82084,82085],{},"Manual deployment is still acceptable when one experienced operator deploys infrequently to a low-risk app and follows a written checklist.",[16,82087,82088],{},"You should move toward automated Django deployment when:",[63,82090,82091,82094,82097,82100,82103,82106],{},[66,82092,82093],{},"releases happen weekly or more often",[66,82095,82096],{},"multiple people deploy",[66,82098,82099],{},"the process depends on memory",[66,82101,82102],{},"rollback is unclear or slow",[66,82104,82105],{},"downtime tolerance is low",[66,82107,82108],{},"you need auditability or repeatable verification",[16,82110,82111],{},"The safest transition is staged:",[1173,82113,82114,82117,82120,82123,82126],{},[66,82115,82116],{},"document the manual release checklist",[66,82118,82119],{},"turn the checklist into a repeatable script",[66,82121,82122],{},"add pre-deploy and post-deploy verification",[66,82124,82125],{},"move orchestration into CI\u002FCD only after the script is stable",[66,82127,82128],{},"define rollback policy before enabling one-click releases",[11,82130,43],{"id":42},[11,82132,82134],{"id":82133},"what-manual-django-deployment-usually-includes","What manual Django deployment usually includes",[16,82136,82137],{},"A typical production Django release on a Linux VM with Gunicorn and Nginx looks like this:",[106,82139,82141],{"className":108,"code":82140,"language":110,"meta":111,"style":111},"ssh deploy@example-server\ncd \u002Fsrv\u002Fmyapp\n\ngit fetch --tags\ngit checkout \u003Crelease-tag>\n\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install -r requirements.txt\n\nexport DJANGO_SETTINGS_MODULE=config.settings.production\n# Application secrets should be loaded by the process manager or secret manager,\n# not exported interactively during deploy.\n\npython manage.py check --deploy\npython manage.py migrate --noinput\npython manage.py collectstatic --noinput\n\nsudo systemctl restart gunicorn\nsudo nginx -t && sudo systemctl reload nginx\n\ncurl -fsS https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,82142,82143,82150,82156,82160,82169,82185,82189,82195,82205,82209,82219,82224,82229,82233,82243,82253,82263,82267,82277,82295,82299],{"__ignoreMap":111},[115,82144,82145,82147],{"class":117,"line":118},[115,82146,14184],{"class":262},[115,82148,82149],{"class":132}," deploy@example-server\n",[115,82151,82152,82154],{"class":117,"line":136},[115,82153,5303],{"class":202},[115,82155,14799],{"class":132},[115,82157,82158],{"class":117,"line":149},[115,82159,310],{"emptyLinePlaceholder":309},[115,82161,82162,82164,82166],{"class":117,"line":162},[115,82163,13525],{"class":262},[115,82165,80996],{"class":132},[115,82167,82168],{"class":202}," --tags\n",[115,82170,82171,82173,82175,82177,82180,82183],{"class":117,"line":175},[115,82172,13525],{"class":262},[115,82174,30452],{"class":132},[115,82176,7691],{"class":121},[115,82178,82179],{"class":132},"release-ta",[115,82181,82182],{"class":125},"g",[115,82184,17380],{"class":121},[115,82186,82187],{"class":117,"line":350},[115,82188,310],{"emptyLinePlaceholder":309},[115,82190,82191,82193],{"class":117,"line":365},[115,82192,5311],{"class":202},[115,82194,5314],{"class":132},[115,82196,82197,82199,82201,82203],{"class":117,"line":380},[115,82198,8618],{"class":262},[115,82200,6600],{"class":132},[115,82202,12350],{"class":202},[115,82204,12353],{"class":132},[115,82206,82207],{"class":117,"line":487},[115,82208,310],{"emptyLinePlaceholder":309},[115,82210,82211,82213,82215,82217],{"class":117,"line":2095},[115,82212,122],{"class":121},[115,82214,77257],{"class":125},[115,82216,129],{"class":121},[115,82218,77262],{"class":125},[115,82220,82221],{"class":117,"line":2104},[115,82222,82223],{"class":3861},"# Application secrets should be loaded by the process manager or secret manager,\n",[115,82225,82226],{"class":117,"line":2113},[115,82227,82228],{"class":3861},"# not exported interactively during deploy.\n",[115,82230,82231],{"class":117,"line":2122},[115,82232,310],{"emptyLinePlaceholder":309},[115,82234,82235,82237,82239,82241],{"class":117,"line":2131},[115,82236,1114],{"class":262},[115,82238,1117],{"class":132},[115,82240,1814],{"class":132},[115,82242,1817],{"class":202},[115,82244,82245,82247,82249,82251],{"class":117,"line":2136},[115,82246,1114],{"class":262},[115,82248,1117],{"class":132},[115,82250,1826],{"class":132},[115,82252,1841],{"class":202},[115,82254,82255,82257,82259,82261],{"class":117,"line":2142},[115,82256,1114],{"class":262},[115,82258,1117],{"class":132},[115,82260,1838],{"class":132},[115,82262,1841],{"class":202},[115,82264,82265],{"class":117,"line":2273},[115,82266,310],{"emptyLinePlaceholder":309},[115,82268,82269,82271,82273,82275],{"class":117,"line":2282},[115,82270,2001],{"class":262},[115,82272,3480],{"class":132},[115,82274,3483],{"class":132},[115,82276,1987],{"class":132},[115,82278,82279,82281,82283,82285,82287,82289,82291,82293],{"class":117,"line":2291},[115,82280,2001],{"class":262},[115,82282,3906],{"class":132},[115,82284,3909],{"class":202},[115,82286,3912],{"class":125},[115,82288,2001],{"class":262},[115,82290,3480],{"class":132},[115,82292,3919],{"class":132},[115,82294,1996],{"class":132},[115,82296,82297],{"class":117,"line":2299},[115,82298,310],{"emptyLinePlaceholder":309},[115,82300,82301,82303,82305],{"class":117,"line":2307},[115,82302,2764],{"class":262},[115,82304,76883],{"class":202},[115,82306,13426],{"class":132},[16,82308,82309],{},"Typical steps:",[63,82311,82312,82315,82317,82320,82323,82326,82328,82331,82334],{},[66,82313,82314],{},"update code or deploy an artifact",[66,82316,59747],{},[66,82318,82319],{},"use the server’s configured runtime environment for secrets",[66,82321,82322],{},"run Django checks",[66,82324,82325],{},"apply migrations",[66,82327,58157],{},[66,82329,82330],{},"restart application workers",[66,82332,82333],{},"validate and reload the reverse proxy",[66,82335,82336],{},"verify the app with a health check",[16,82338,82339],{},"This manual path still works for some teams when:",[63,82341,82342,82345,82348,82351],{},[66,82343,82344],{},"the app is small",[66,82346,82347],{},"releases are rare",[66,82349,82350],{},"one maintainer owns production",[66,82352,82353],{},"a short maintenance window is acceptable",[11,82355,82357],{"id":82356},"the-limits-of-manual-deployment-in-production","The limits of manual deployment in production",[16,82359,82360],{},"Manual deployments fail in predictable ways.",[52,82362,82364],{"id":82363},"common-failure-points","Common failure points",[63,82366,82367,82373,82379,82385,82391],{},[66,82368,82369,82372],{},[1226,82370,82371],{},"Missed migrations:"," code is live before schema is updated, or vice versa.",[66,82374,82375,82378],{},[1226,82376,82377],{},"Dependency drift:"," the server virtualenv differs from what was tested.",[66,82380,82381,82384],{},[1226,82382,82383],{},"Static files mismatch:"," new templates reference assets that were not collected.",[66,82386,82387,82390],{},[1226,82388,82389],{},"Wrong service order:"," Gunicorn restarts before required changes are ready.",[66,82392,82393,82396],{},[1226,82394,82395],{},"No health verification:"," deployment “looks done” but requests fail.",[16,82398,82399],{},"Use verification after each critical change:",[106,82401,82403],{"className":108,"code":82402,"language":110,"meta":111,"style":111},"python manage.py check --deploy\nsudo systemctl status gunicorn --no-pager\nsudo nginx -t\ncurl -fsS https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,82404,82405,82415,82427,82435],{"__ignoreMap":111},[115,82406,82407,82409,82411,82413],{"class":117,"line":118},[115,82408,1114],{"class":262},[115,82410,1117],{"class":132},[115,82412,1814],{"class":132},[115,82414,1817],{"class":202},[115,82416,82417,82419,82421,82423,82425],{"class":117,"line":136},[115,82418,2001],{"class":262},[115,82420,3480],{"class":132},[115,82422,1984],{"class":132},[115,82424,2791],{"class":132},[115,82426,2800],{"class":202},[115,82428,82429,82431,82433],{"class":117,"line":149},[115,82430,2001],{"class":262},[115,82432,3906],{"class":132},[115,82434,4282],{"class":202},[115,82436,82437,82439,82441],{"class":117,"line":162},[115,82438,2764],{"class":262},[115,82440,76883],{"class":202},[115,82442,13426],{"class":132},[52,82444,82446],{"id":82445},"security-and-audit-risks","Security and audit risks",[16,82448,82449],{},"Manual deployment also has operational security costs:",[63,82451,82452,82455,82458,82461],{},[66,82453,82454],{},"every deploy requires direct shell access",[66,82456,82457],{},"secrets may be handled inconsistently",[66,82459,82460],{},"there may be no reliable deployment log",[66,82462,82463],{},"server permissions and runtime environment can drift over time",[16,82465,82466],{},"A safer baseline is to keep secrets outside the deploy command path. For example, with systemd, load environment from a root-managed file rather than exporting values interactively:",[106,82468,82470],{"className":2026,"code":82469,"language":2028,"meta":111,"style":111},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fgunicorn.service\n[Service]\nUser=myapp\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000\nRestart=on-failure\nRestartSec=5\n",[20,82471,82472,82476,82480,82486,82492,82498,82504,82511,82517],{"__ignoreMap":111},[115,82473,82474],{"class":117,"line":118},[115,82475,45135],{"class":3861},[115,82477,82478],{"class":117,"line":136},[115,82479,2060],{"class":262},[115,82481,82482,82484],{"class":117,"line":149},[115,82483,2065],{"class":121},[115,82485,36620],{"class":125},[115,82487,82488,82490],{"class":117,"line":162},[115,82489,2073],{"class":121},[115,82491,2076],{"class":125},[115,82493,82494,82496],{"class":117,"line":175},[115,82495,2081],{"class":121},[115,82497,2084],{"class":125},[115,82499,82500,82502],{"class":117,"line":350},[115,82501,2089],{"class":121},[115,82503,4912],{"class":125},[115,82505,82506,82508],{"class":117,"line":365},[115,82507,2107],{"class":121},[115,82509,82510],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn config.wsgi:application --bind 127.0.0.1:8000\n",[115,82512,82513,82515],{"class":117,"line":380},[115,82514,2116],{"class":121},[115,82516,2119],{"class":125},[115,82518,82519,82521],{"class":117,"line":487},[115,82520,2125],{"class":121},[115,82522,2128],{"class":125},[16,82524,82525,82526,82529],{},"Ensure ",[20,82527,82528],{},"\u002Fetc\u002Fmyapp\u002Fmyapp.env"," is readable by root and not writable by the app user.",[16,82531,73379],{},[106,82533,82535],{"className":108,"code":82534,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl restart gunicorn\nsudo systemctl status gunicorn --no-pager\n",[20,82536,82537,82545,82555],{"__ignoreMap":111},[115,82538,82539,82541,82543],{"class":117,"line":118},[115,82540,2001],{"class":262},[115,82542,3480],{"class":132},[115,82544,4984],{"class":132},[115,82546,82547,82549,82551,82553],{"class":117,"line":136},[115,82548,2001],{"class":262},[115,82550,3480],{"class":132},[115,82552,3483],{"class":132},[115,82554,1987],{"class":132},[115,82556,82557,82559,82561,82563,82565],{"class":117,"line":149},[115,82558,2001],{"class":262},[115,82560,3480],{"class":132},[115,82562,1984],{"class":132},[115,82564,2791],{"class":132},[115,82566,2800],{"class":202},[16,82568,82569,82570,34069,82572,1153,82574,82576,82577,82579],{},"If Django runs behind Nginx or another reverse proxy, verify production settings such as ",[20,82571,2707],{},[20,82573,2719],{},[20,82575,2725],{},", and proxy-aware HTTPS settings are configured correctly. ",[20,82578,20349],{}," helps, but it does not replace reviewing your production settings.",[52,82581,82583],{"id":82582},"rollback-problems-with-manual-releases","Rollback problems with manual releases",[16,82585,82586],{},"The biggest weakness of manual deployment is recovery. If a release fails, you need:",[63,82588,82589,82592,82595,82598],{},[66,82590,82591],{},"a known previous version",[66,82593,82594],{},"a clear restart sequence",[66,82596,82597],{},"a database rollback policy",[66,82599,82600],{},"a way to verify recovery",[16,82602,82603],{},"Only do this if the previous application version is compatible with the current database schema; code rollback alone is not a safe recovery plan after every migration.",[16,82605,82606],{},"A simple code rollback might be:",[106,82608,82610],{"className":108,"code":82609,"language":110,"meta":111,"style":111},"git checkout \u003Cprevious-release-tag>\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install -r requirements.txt\npython manage.py collectstatic --noinput\nsudo systemctl restart gunicorn\ncurl -fsS https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,82611,82612,82627,82633,82643,82653,82663],{"__ignoreMap":111},[115,82613,82614,82616,82618,82620,82623,82625],{"class":117,"line":118},[115,82615,13525],{"class":262},[115,82617,30452],{"class":132},[115,82619,7691],{"class":121},[115,82621,82622],{"class":132},"previous-release-ta",[115,82624,82182],{"class":125},[115,82626,17380],{"class":121},[115,82628,82629,82631],{"class":117,"line":136},[115,82630,5311],{"class":202},[115,82632,5314],{"class":132},[115,82634,82635,82637,82639,82641],{"class":117,"line":149},[115,82636,8618],{"class":262},[115,82638,6600],{"class":132},[115,82640,12350],{"class":202},[115,82642,12353],{"class":132},[115,82644,82645,82647,82649,82651],{"class":117,"line":162},[115,82646,1114],{"class":262},[115,82648,1117],{"class":132},[115,82650,1838],{"class":132},[115,82652,1841],{"class":202},[115,82654,82655,82657,82659,82661],{"class":117,"line":175},[115,82656,2001],{"class":262},[115,82658,3480],{"class":132},[115,82660,3483],{"class":132},[115,82662,1987],{"class":132},[115,82664,82665,82667,82669],{"class":117,"line":350},[115,82666,2764],{"class":262},[115,82668,76883],{"class":202},[115,82670,13426],{"class":132},[16,82672,82673],{},"That only rolls back application code. Database rollback is separate and should not be assumed safe. Destructive, backward-incompatible, or long-running migrations need review before deployment.",[11,82675,82677],{"id":82676},"clear-signals-that-it-is-time-to-switch-to-automated-django-deployment","Clear signals that it is time to switch to automated Django deployment",[16,82679,82680],{},"Move away from a fully manual Django deployment workflow when you see these signals.",[52,82682,82684],{"id":82683},"team-and-workflow-signals","Team and workflow signals",[63,82686,82687,82690,82692,82695],{},[66,82688,82689],{},"more than one person deploys",[66,82691,82093],{},[66,82693,82694],{},"you rely on a checklist to avoid mistakes",[66,82696,82697],{},"production fixes are delayed because deployment feels risky",[52,82699,82701],{"id":82700},"technical-signals","Technical signals",[63,82703,82704,82707,82710,82713],{},[66,82705,82706],{},"you need low-downtime or zero-downtime releases",[66,82708,82709],{},"Celery workers or scheduled jobs must be coordinated with web deploys",[66,82711,82712],{},"staging and production environments drift",[66,82714,82715],{},"you repeat the same server setup across projects",[52,82717,82719],{"id":82718},"compliance-and-reliability-signals","Compliance and reliability signals",[63,82721,82722,82725,82728],{},[66,82723,82724],{},"you need deployment logs or approval gates",[66,82726,82727],{},"you need predictable rollback",[66,82729,82730],{},"you need health checks before considering a release complete",[11,82732,82734],{"id":82733},"what-to-automate-first","What to automate first",[16,82736,82737],{},"Do not start by building a complex CI\u002FCD system. Start by making the release path repeatable.",[52,82739,82741],{"id":82740},"start-with-a-release-script","Start with a release script",[16,82743,82744],{},"Put the current release steps into one ordered script and make it fail fast:",[106,82746,82748],{"className":108,"code":82747,"language":110,"meta":111,"style":111},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nif [ $# -ne 1 ]; then\n  echo \"Usage: $0 \u003Crelease-tag-or-commit>\"\n  exit 1\nfi\n\ncd \u002Fsrv\u002Fmyapp\ngit fetch --tags\ngit checkout \"$1\"\n\nsource \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Factivate\npip install -r requirements.txt\n\npython manage.py check --deploy\npython manage.py migrate --noinput\npython manage.py collectstatic --noinput\n\nsudo systemctl restart gunicorn\nsudo nginx -t && sudo systemctl reload nginx\n\ncurl -fsS https:\u002F\u002Fexample.com\u002Fhealth\u002F\n",[20,82749,82750,82754,82762,82766,82784,82795,82801,82805,82809,82815,82823,82836,82840,82846,82856,82860,82870,82880,82890,82894,82904,82922,82926],{"__ignoreMap":111},[115,82751,82752],{"class":117,"line":118},[115,82753,80307],{"class":3861},[115,82755,82756,82758,82760],{"class":117,"line":136},[115,82757,203],{"class":202},[115,82759,80314],{"class":202},[115,82761,80317],{"class":132},[115,82763,82764],{"class":117,"line":149},[115,82765,310],{"emptyLinePlaceholder":309},[115,82767,82768,82770,82772,82775,82778,82780,82782],{"class":117,"line":162},[115,82769,10833],{"class":121},[115,82771,66868],{"class":125},[115,82773,82774],{"class":202},"$#",[115,82776,82777],{"class":121}," -ne",[115,82779,80596],{"class":202},[115,82781,66881],{"class":125},[115,82783,66884],{"class":121},[115,82785,82786,82788,82790,82792],{"class":117,"line":175},[115,82787,66889],{"class":202},[115,82789,80494],{"class":132},[115,82791,80497],{"class":202},[115,82793,82794],{"class":132}," \u003Crelease-tag-or-commit>\"\n",[115,82796,82797,82799],{"class":117,"line":350},[115,82798,80505],{"class":202},[115,82800,8995],{"class":202},[115,82802,82803],{"class":117,"line":365},[115,82804,66962],{"class":121},[115,82806,82807],{"class":117,"line":380},[115,82808,310],{"emptyLinePlaceholder":309},[115,82810,82811,82813],{"class":117,"line":487},[115,82812,5303],{"class":202},[115,82814,14799],{"class":132},[115,82816,82817,82819,82821],{"class":117,"line":2095},[115,82818,13525],{"class":262},[115,82820,80996],{"class":132},[115,82822,82168],{"class":202},[115,82824,82825,82827,82829,82831,82834],{"class":117,"line":2104},[115,82826,13525],{"class":262},[115,82828,30452],{"class":132},[115,82830,325],{"class":132},[115,82832,82833],{"class":202},"$1",[115,82835,391],{"class":132},[115,82837,82838],{"class":117,"line":2113},[115,82839,310],{"emptyLinePlaceholder":309},[115,82841,82842,82844],{"class":117,"line":2122},[115,82843,5311],{"class":202},[115,82845,5314],{"class":132},[115,82847,82848,82850,82852,82854],{"class":117,"line":2131},[115,82849,8618],{"class":262},[115,82851,6600],{"class":132},[115,82853,12350],{"class":202},[115,82855,12353],{"class":132},[115,82857,82858],{"class":117,"line":2136},[115,82859,310],{"emptyLinePlaceholder":309},[115,82861,82862,82864,82866,82868],{"class":117,"line":2142},[115,82863,1114],{"class":262},[115,82865,1117],{"class":132},[115,82867,1814],{"class":132},[115,82869,1817],{"class":202},[115,82871,82872,82874,82876,82878],{"class":117,"line":2273},[115,82873,1114],{"class":262},[115,82875,1117],{"class":132},[115,82877,1826],{"class":132},[115,82879,1841],{"class":202},[115,82881,82882,82884,82886,82888],{"class":117,"line":2282},[115,82883,1114],{"class":262},[115,82885,1117],{"class":132},[115,82887,1838],{"class":132},[115,82889,1841],{"class":202},[115,82891,82892],{"class":117,"line":2291},[115,82893,310],{"emptyLinePlaceholder":309},[115,82895,82896,82898,82900,82902],{"class":117,"line":2299},[115,82897,2001],{"class":262},[115,82899,3480],{"class":132},[115,82901,3483],{"class":132},[115,82903,1987],{"class":132},[115,82905,82906,82908,82910,82912,82914,82916,82918,82920],{"class":117,"line":2307},[115,82907,2001],{"class":262},[115,82909,3906],{"class":132},[115,82911,3909],{"class":202},[115,82913,3912],{"class":125},[115,82915,2001],{"class":262},[115,82917,3480],{"class":132},[115,82919,3919],{"class":132},[115,82921,1996],{"class":132},[115,82923,82924],{"class":117,"line":2315},[115,82925,310],{"emptyLinePlaceholder":309},[115,82927,82928,82930,82932],{"class":117,"line":2320},[115,82929,2764],{"class":262},[115,82931,76883],{"class":202},[115,82933,13426],{"class":132},[16,82935,82936],{},"This removes operator variance and makes failures visible earlier.",[52,82938,82940],{"id":82939},"add-pre-deploy-and-post-deploy-checks","Add pre-deploy and post-deploy checks",[16,82942,82943],{},"Useful first checks:",[63,82945,82946,82949,82952,82956,82959],{},[66,82947,82948],{},"required environment file exists",[66,82950,82951],{},"database connectivity works",[66,82953,82954,32448],{},[20,82955,15970],{},[66,82957,82958],{},"the selected release tag or artifact is recorded",[66,82960,82961],{},"the public health endpoint returns success after restart",[16,82963,82964],{},"For troubleshooting after deploy:",[106,82966,82968],{"className":108,"code":82967,"language":110,"meta":111,"style":111},"sudo systemctl status gunicorn --no-pager\nsudo journalctl -u gunicorn -n 50 --no-pager\nsudo nginx -t\n",[20,82969,82970,82982,82998],{"__ignoreMap":111},[115,82971,82972,82974,82976,82978,82980],{"class":117,"line":118},[115,82973,2001],{"class":262},[115,82975,3480],{"class":132},[115,82977,1984],{"class":132},[115,82979,2791],{"class":132},[115,82981,2800],{"class":202},[115,82983,82984,82986,82988,82990,82992,82994,82996],{"class":117,"line":136},[115,82985,2001],{"class":262},[115,82987,5030],{"class":132},[115,82989,2788],{"class":202},[115,82991,2791],{"class":132},[115,82993,2794],{"class":202},[115,82995,15523],{"class":202},[115,82997,2800],{"class":202},[115,82999,83000,83002,83004],{"class":117,"line":149},[115,83001,2001],{"class":262},[115,83003,3906],{"class":132},[115,83005,4282],{"class":202},[52,83007,83009],{"id":83008},"prepare-rollback-before-automating-full-rollback","Prepare rollback before automating full rollback",[16,83011,83012],{},"Before adding one-click deployment, keep:",[63,83014,83015,83018,83021,83024],{},[66,83016,83017],{},"a previous release tag or artifact",[66,83019,83020],{},"release metadata such as version and deploy time",[66,83022,83023],{},"an explicit migration policy for unsafe schema changes",[66,83025,83026],{},"a documented rule for when code rollback is allowed",[16,83028,83029],{},"A simple pattern is to record the deployed release tag in a file on the server.",[11,83031,83033],{"id":83032},"what-should-stay-manual-at-first","What should stay manual at first",[16,83035,83036],{},"Some parts of production deployment should remain reviewed by a human initially.",[52,83038,83040],{"id":83039},"high-risk-database-changes","High-risk database changes",[16,83042,83043],{},"Keep these manual or gated:",[63,83045,83046,83049,83052,83055],{},[66,83047,83048],{},"destructive migrations",[66,83050,83051],{},"long-running schema changes",[66,83053,83054],{},"data migrations that need operator review",[66,83056,83057],{},"changes where old and new code are not both compatible with the schema during rollout",[52,83059,83061],{"id":83060},"one-off-infrastructure-changes","One-off infrastructure changes",[16,83063,83064],{},"Do not mix these into routine app deployment until well tested:",[63,83066,83067,83070,83073,83076],{},[66,83068,83069],{},"DNS cutovers",[66,83071,83072],{},"major TLS changes",[66,83074,83075],{},"initial server hardening",[66,83077,83078],{},"reverse proxy redesign",[52,83080,83082],{"id":83081},"incident-decisions","Incident decisions",[16,83084,83085],{},"Automation can execute a rollback, but it should not decide:",[63,83087,83088,83091,83094],{},[66,83089,83090],{},"whether to hotfix or roll back",[66,83092,83093],{},"whether to pause workers",[66,83095,83096],{},"whether to disable a feature",[52,83098,83100],{"id":83099},"when-scripts-and-templates-become-useful","When scripts and templates become useful",[16,83102,83103],{},"Once your deployment script is stable across multiple releases, it becomes a good candidate for a reusable template. The same is true for CI workflow files, systemd service definitions, and release directory layouts. The goal is not to automate everything immediately, but to stop rewriting the same trusted deployment logic for each project.",[11,83105,83107],{"id":83106},"a-practical-maturity-path-from-manual-to-automated","A practical maturity path from manual to automated",[52,83109,83111],{"id":83110},"stage-1-manual-deployment-with-a-written-checklist","Stage 1: Manual deployment with a written checklist",[16,83113,83114],{},"Document the exact commands, expected outputs, verification checks, and rollback notes.",[52,83116,83118],{"id":83117},"stage-2-local-or-server-side-deployment-script","Stage 2: Local or server-side deployment script",[16,83120,83121],{},"Run one command instead of six or seven separate commands. This is often the highest-value first step.",[52,83123,83125],{"id":83124},"stage-3-ci-driven-automated-django-deployment","Stage 3: CI-driven automated Django deployment",[16,83127,83128],{},"Once the script is stable, let CI run tests, build an artifact, and call the remote deploy script.",[16,83130,83131],{},"Example job shape:",[106,83133,83135],{"className":2485,"code":83134,"language":2487,"meta":111,"style":111},"steps:\n  - run tests\n  - build release artifact\n  - upload artifact\n  - run remote deploy script\n  - run health check\n",[20,83136,83137,83144,83151,83158,83165,83172],{"__ignoreMap":111},[115,83138,83139,83142],{"class":117,"line":118},[115,83140,83141],{"class":2494},"steps",[115,83143,2498],{"class":125},[115,83145,83146,83148],{"class":117,"line":136},[115,83147,20762],{"class":125},[115,83149,83150],{"class":132},"run tests\n",[115,83152,83153,83155],{"class":117,"line":149},[115,83154,20762],{"class":125},[115,83156,83157],{"class":132},"build release artifact\n",[115,83159,83160,83162],{"class":117,"line":162},[115,83161,20762],{"class":125},[115,83163,83164],{"class":132},"upload artifact\n",[115,83166,83167,83169],{"class":117,"line":175},[115,83168,20762],{"class":125},[115,83170,83171],{"class":132},"run remote deploy script\n",[115,83173,83174,83176],{"class":117,"line":350},[115,83175,20762],{"class":125},[115,83177,83178],{"class":132},"run health check\n",[52,83180,83182],{"id":83181},"stage-4-full-repeatable-release-workflow","Stage 4: Full repeatable release workflow",[16,83184,83185],{},"At this point, add:",[63,83187,83188,83191,83194,83197,83200],{},[66,83189,83190],{},"environment-specific config handling",[66,83192,83193],{},"release logging",[66,83195,83196],{},"automated health checks",[66,83198,83199],{},"notifications",[66,83201,83202],{},"rollback execution based on a known previous version",[11,83204,83206],{"id":83205},"how-to-switch-without-breaking-production","How to switch without breaking production",[1173,83208,83209,83215,83221,83227],{},[66,83210,83211,83214],{},[1226,83212,83213],{},"Standardize the manual process first."," If the current process is inconsistent, automation will preserve the inconsistency.",[66,83216,83217,83220],{},[1226,83218,83219],{},"Test in staging first."," Match production services closely, especially PostgreSQL, Redis, static files, process supervision, and proxy behavior.",[66,83222,83223,83226],{},[1226,83224,83225],{},"Add release verification gates."," Check app health, process status, and logs before declaring success.",[66,83228,83229,83232],{},[1226,83230,83231],{},"Define rollback first."," Know the previous version source, service restart order, worker coordination, and database rollback policy.",[11,83234,1321],{"id":1320},[16,83236,83237],{},"The reason automated Django deployment matters is not that SSH-based deployment is inherently wrong. It is that production reliability depends on consistency. Manual processes rely on memory and individual habits. Automated processes rely on ordered steps, failure handling, and verification.",[16,83239,83240],{},"For a single low-traffic app on one VM, a documented manual process can still be appropriate. For a team shipping often, a production Django deployment workflow should be scripted at minimum and usually moved into CI\u002FCD. The main benefit is not speed alone. It is that each release follows the same path, creates a deployment record, and supports faster recovery.",[16,83242,83243],{},"For Docker-based deployments, automation usually means building an image, pushing it to a registry, and rolling out containers rather than running SSH commands directly. The same decision logic still applies: if releases are frequent and rollback needs to be predictable, automate the release path. For non-Docker deployments, a shell script plus systemd is often a practical midpoint before full CI\u002FCD orchestration.",[11,83245,1337],{"id":1336},[63,83247,83248,83254,83260,83266,83274,83281,83289,83295],{},[66,83249,83250,83253],{},[1226,83251,83252],{},"Docker vs non-Docker:"," in Docker setups, “deploy” often means image build, migration job, container rollout, and health check rather than Git checkout on the host.",[66,83255,83256,83259],{},[1226,83257,83258],{},"Single VM vs multiple servers:"," coordination becomes more important when web workers, Celery workers, Redis, and cron jobs must stay in sync.",[66,83261,83262,83265],{},[1226,83263,83264],{},"Database migrations:"," do not blindly automate unsafe migrations. Separate safe schema changes from high-risk changes that need operator approval.",[66,83267,83268,83270,83271,83273],{},[1226,83269,10126],{}," if static files are served by Nginx from a shared directory, verify ",[20,83272,13689],{}," completes before restarting app workers.",[66,83275,83276,2957,83278,83280],{},[1226,83277,27007],{},[20,83279,13689],{}," does not handle user uploads. Keep your media storage and backup strategy separate from static file deployment.",[66,83282,83283,83286,83287,211],{},[1226,83284,83285],{},"Health endpoints:"," use an endpoint that confirms the app can start and serve requests, not just that Nginx returns ",[20,83288,17741],{},[66,83290,83291,83294],{},[1226,83292,83293],{},"HTTPS and proxy headers:"," if Django is behind Nginx, make sure forwarded headers and Django HTTPS settings are aligned so redirects, CSRF checks, and secure cookies behave correctly.",[66,83296,83297,83300],{},[1226,83298,83299],{},"Secrets handling:"," keep secrets in managed environment files or a secret manager, not in shell history or CI logs.",[11,83302,1386],{"id":1385},[16,83304,83305,83306,211],{},"For the baseline release path, see ",[1395,83307,32365],{"href":2999},[16,83309,83310,83311,211],{},"If you want the concrete Linux server workflow behind the manual side of this decision, read ",[1395,83312,2986],{"href":2985},[16,83314,83315,83316,211],{},"For production settings that often break behind a reverse proxy, review ",[1395,83317,3007],{"href":3006},[16,83319,83320,83321,211],{},"If you are ready to move from scripts to orchestration, continue with ",[1395,83322,83323],{"href":40120},"Set up CI\u002FCD for Django deployment",[16,83325,83326,83327,83330],{},"For recovery planning, keep ",[1395,83328,83329],{"href":1415},"How to roll back a Django deployment safely"," next to your release process.",[11,83332,1420],{"id":1419},[11,83334,83336],{"id":83335},"when-is-manual-django-deployment-still-acceptable","When is manual Django deployment still acceptable?",[16,83338,83339],{},"It is acceptable when one experienced person deploys infrequently, traffic is low, downtime tolerance is reasonable, and the process is documented with verification and rollback notes.",[11,83341,83343],{"id":83342},"what-is-the-first-part-of-a-django-deployment-i-should-automate","What is the first part of a Django deployment I should automate?",[16,83345,83346],{},"Start with a repeatable release script that updates code or activates the chosen artifact, runs checks, migrations, static file collection, service restart, and health verification in the correct order.",[11,83348,83350],{"id":83349},"should-database-migrations-be-fully-automated-in-production","Should database migrations be fully automated in production?",[16,83352,83353],{},"Only safe and reviewed migrations should be automated by default. Destructive, long-running, or data-sensitive migrations usually need explicit review and a rollback plan.",[11,83355,83357],{"id":83356},"is-automated-django-deployment-worth-it-for-a-single-small-app","Is automated Django deployment worth it for a single small app?",[16,83359,83360],{},"Often yes, but start small. A shell script and a written rollback procedure may be enough. Full CI\u002FCD is usually justified once releases become frequent or another person needs to deploy.",[11,83362,83364],{"id":83363},"how-do-i-add-rollback-safety-before-moving-to-cicd","How do I add rollback safety before moving to CI\u002FCD?",[16,83366,83367],{},"Keep previous release tags or artifacts, document the service restart sequence, define a database rollback policy, and verify that the previous code version is compatible with the current schema before automating the release trigger.",[1485,83369,83370],{},"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 .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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":111,"searchDepth":149,"depth":149,"links":83372},[83373,83374,83375,83376,83377,83382,83387,83392,83398,83404,83405,83406,83407,83408,83409,83410,83411,83412,83413],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":82133,"depth":136,"text":82134},{"id":82356,"depth":136,"text":82357,"children":83378},[83379,83380,83381],{"id":82363,"depth":149,"text":82364},{"id":82445,"depth":149,"text":82446},{"id":82582,"depth":149,"text":82583},{"id":82676,"depth":136,"text":82677,"children":83383},[83384,83385,83386],{"id":82683,"depth":149,"text":82684},{"id":82700,"depth":149,"text":82701},{"id":82718,"depth":149,"text":82719},{"id":82733,"depth":136,"text":82734,"children":83388},[83389,83390,83391],{"id":82740,"depth":149,"text":82741},{"id":82939,"depth":149,"text":82940},{"id":83008,"depth":149,"text":83009},{"id":83032,"depth":136,"text":83033,"children":83393},[83394,83395,83396,83397],{"id":83039,"depth":149,"text":83040},{"id":83060,"depth":149,"text":83061},{"id":83081,"depth":149,"text":83082},{"id":83099,"depth":149,"text":83100},{"id":83106,"depth":136,"text":83107,"children":83399},[83400,83401,83402,83403],{"id":83110,"depth":149,"text":83111},{"id":83117,"depth":149,"text":83118},{"id":83124,"depth":149,"text":83125},{"id":83181,"depth":149,"text":83182},{"id":83205,"depth":136,"text":83206},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420},{"id":83335,"depth":136,"text":83336},{"id":83342,"depth":136,"text":83343},{"id":83349,"depth":136,"text":83350},{"id":83356,"depth":136,"text":83357},{"id":83363,"depth":136,"text":83364},"Many Django teams start with a manual deployment process over SSH. That is normal: log into a Linux server, pull code, install dependencies, run migrations, collect static files...",{},"\u002Fmanual-vs-automated-django-deployment",[1409,40223,35630],{"title":82067,"description":83414},[1557,28939],"manual-vs-automated-django-deployment",[1557,28939],"PJhwg23yTsJQdjW8bzThapixsbhA3Ku2MLvr7Lwdj0c",{"id":83424,"title":83425,"body":83426,"category":1541,"description":85046,"difficulty":23035,"extension":1544,"funnel_stage":23036,"intent":1546,"meta":85047,"navigation":309,"path":85048,"priority":6330,"related":85049,"role":23035,"section":1554,"seo":85050,"stack":85051,"stem":85052,"tags":85053,"type":1561,"__hash__":85054},"articles\u002Fterraform-django-server-baseline.md","Provision a Django Server Baseline with Terraform",{"type":8,"value":83427,"toc":85019},[83428,83430,83433,83436,83446,83448,83451,83454,83461,83463,83467,83470,83496,83502,83519,83523,83526,83532,83539,83568,83575,83577,83594,83601,83631,83635,83641,83646,83747,83750,83754,83759,83886,83890,83925,83931,83935,83940,84153,84160,84168,84171,84175,84182,84187,84469,84471,84504,84508,84513,84568,84572,84574,84621,84624,84641,84645,84648,84660,84663,84675,84678,84769,84771,84799,84802,84816,84818,84827,84830,84847,84850,84864,84867,84876,84879,84890,84893,84897,84900,84902,84945,84947,84953,84958,84964,84970,84972,84976,84979,84983,84986,84990,84996,85000,85009,85013,85016],[11,83429,14],{"id":13},[16,83431,83432],{},"Manual server creation is one of the fastest ways to introduce deployment drift into a Django environment. One VPS has SSH password login disabled, another does not. One host has a firewall, another exposes too many ports. A third server was bootstrapped by hand months ago and nobody remembers which packages or hardening steps were applied.",[16,83434,83435],{},"For Django production work, that inconsistency becomes an operations problem. App deploys fail on one host and succeed on another. SSH access is fragile. Security settings vary. Recovery is slower because rebuilding the server is not repeatable.",[16,83437,83438,83439,83442,83443,83445],{},"This page shows how to provision a ",[1226,83440,83441],{},"repeatable production server baseline"," for Django with Terraform. The scope is intentionally limited to infrastructure baseline: server creation, firewall, SSH access, and first-boot bootstrap with cloud-init. It does ",[1226,83444,7474],{}," cover deploying Django application code, running migrations, issuing TLS certificates, or configuring the final app reverse proxy.",[11,83447,30],{"id":29},[16,83449,83450],{},"Use Terraform to provision a Linux server with a known image, size, region, SSH keys, firewall policy, and a first-boot bootstrap script via cloud-init. Create a non-root admin user, disable SSH password and keyboard-interactive authentication, enable security updates, and install only baseline packages.",[16,83452,83453],{},"Keep provider credentials out of the repository, avoid putting app secrets into Terraform variables, and use remote state with access controls if this is a shared or production project. Before deploying Django, verify SSH access, provider firewall exposure, cloud-init completion, and the expected OS and package baseline.",[16,83455,83456,83457,83460],{},"Use version-controlled Terraform changes and review ",[20,83458,83459],{},"plan"," output carefully. Rollback may mean reverting Terraform configuration and applying again, but bootstrap and immutable instance changes often require server replacement rather than in-place rollback. Keep durable data outside the app server.",[11,83462,43],{"id":42},[52,83464,83466],{"id":83465},"_1-define-the-server-baseline","1. Define the server baseline",[16,83468,83469],{},"For a Django host, the baseline should usually include:",[63,83471,83472,83475,83478,83481,83484,83487,83490,83493],{},[66,83473,83474],{},"Ubuntu LTS or another stable Linux image",[66,83476,83477],{},"SSH key access only",[66,83479,83480],{},"A non-root administrative or deploy user",[66,83482,83483],{},"Basic provider firewall rules",[66,83485,83486],{},"Security update policy",[66,83488,83489],{},"Time sync, logging, and basic intrusion protection such as fail2ban",[66,83491,83492],{},"Optional runtime bootstrap such as Docker or Python build dependencies",[66,83494,83495],{},"Consistent naming and tags",[16,83497,83498,83499,83501],{},"This page does ",[1226,83500,7474],{}," provision:",[63,83503,83504,83507,83510,83513,83516],{},[66,83505,83506],{},"Django code release",[66,83508,83509],{},"database migrations",[66,83511,83512],{},"TLS certificates",[66,83514,83515],{},"Nginx site config for the app",[66,83517,83518],{},"full monitoring or backup stacks",[52,83520,83522],{"id":83521},"_2-use-a-maintainable-terraform-layout","2. Use a maintainable Terraform layout",[16,83524,83525],{},"A simple layout is enough for a single server baseline:",[106,83527,83530],{"className":83528,"code":83529,"language":247,"meta":111},[245],".\n├── providers.tf\n├── variables.tf\n├── main.tf\n├── outputs.tf\n├── terraform.tfvars\n└── cloud-init.yaml\n",[20,83531,83529],{"__ignoreMap":111},[16,83533,83534,83535,83538],{},"Keep environment-specific values separate in ",[20,83536,83537],{},"terraform.tfvars",", such as:",[63,83540,83541,83546,83550,83555,83560,83565],{},[66,83542,83543],{},[20,83544,83545],{},"region",[66,83547,83548],{},[20,83549,74317],{},[66,83551,83552],{},[20,83553,83554],{},"image",[66,83556,83557],{},[20,83558,83559],{},"allowed_ssh_cidrs",[66,83561,83562],{},[20,83563,83564],{},"hostname",[66,83566,83567],{},"tags",[16,83569,83570,83571,83574],{},"Do not store provider tokens in ",[20,83572,83573],{},".tfvars",". Export them as environment variables instead.",[16,83576,12414],{},[106,83578,83580],{"className":108,"code":83579,"language":110,"meta":111,"style":111},"export DIGITALOCEAN_TOKEN='your-token-here'\n",[20,83581,83582],{"__ignoreMap":111},[115,83583,83584,83586,83589,83591],{"class":117,"line":118},[115,83585,122],{"class":121},[115,83587,83588],{"class":125}," DIGITALOCEAN_TOKEN",[115,83590,129],{"class":121},[115,83592,83593],{"class":132},"'your-token-here'\n",[16,83595,83596,83597,83600],{},"If you use Git, ensure ",[20,83598,83599],{},".gitignore"," includes sensitive local files:",[106,83602,83604],{"className":18822,"code":83603,"language":18824,"meta":111,"style":111},".terraform\u002F\n*.tfstate\n*.tfstate.*\n*.tfplan\nterraform.tfvars\n",[20,83605,83606,83611,83616,83621,83626],{"__ignoreMap":111},[115,83607,83608],{"class":117,"line":118},[115,83609,83610],{},".terraform\u002F\n",[115,83612,83613],{"class":117,"line":136},[115,83614,83615],{},"*.tfstate\n",[115,83617,83618],{"class":117,"line":149},[115,83619,83620],{},"*.tfstate.*\n",[115,83622,83623],{"class":117,"line":162},[115,83624,83625],{},"*.tfplan\n",[115,83627,83628],{"class":117,"line":175},[115,83629,83630],{},"terraform.tfvars\n",[52,83632,83634],{"id":83633},"_3-configure-the-provider-and-remote-state","3. Configure the provider and remote state",[16,83636,12229,83637,83640],{},[1226,83638,83639],{},"DigitalOcean"," to keep the provider syntax consistent. The same pattern applies to EC2, Hetzner, or other VPS providers.",[16,83642,83643,241],{},[20,83644,83645],{},"providers.tf",[106,83647,83651],{"className":83648,"code":83649,"language":83650,"meta":111,"style":111},"language-hcl shiki shiki-themes github-light github-dark","terraform {\n  required_version = \">= 1.5.0\"\n\n  required_providers {\n    digitalocean = {\n      source  = \"digitalocean\u002Fdigitalocean\"\n      version = \"~> 2.0\"\n    }\n  }\n\n  backend \"s3\" {\n    bucket         = \"my-terraform-state\"\n    key            = \"django-server-baseline\u002Fterraform.tfstate\"\n    region         = \"us-east-1\"\n    encrypt        = true\n    dynamodb_table = \"terraform-locks\"\n  }\n}\n\nprovider \"digitalocean\" {}\n","hcl",[20,83652,83653,83658,83663,83667,83672,83677,83682,83687,83691,83696,83700,83705,83710,83715,83720,83725,83730,83734,83738,83742],{"__ignoreMap":111},[115,83654,83655],{"class":117,"line":118},[115,83656,83657],{},"terraform {\n",[115,83659,83660],{"class":117,"line":136},[115,83661,83662],{},"  required_version = \">= 1.5.0\"\n",[115,83664,83665],{"class":117,"line":149},[115,83666,310],{"emptyLinePlaceholder":309},[115,83668,83669],{"class":117,"line":162},[115,83670,83671],{},"  required_providers {\n",[115,83673,83674],{"class":117,"line":175},[115,83675,83676],{},"    digitalocean = {\n",[115,83678,83679],{"class":117,"line":350},[115,83680,83681],{},"      source  = \"digitalocean\u002Fdigitalocean\"\n",[115,83683,83684],{"class":117,"line":365},[115,83685,83686],{},"      version = \"~> 2.0\"\n",[115,83688,83689],{"class":117,"line":380},[115,83690,2233],{},[115,83692,83693],{"class":117,"line":487},[115,83694,83695],{},"  }\n",[115,83697,83698],{"class":117,"line":2095},[115,83699,310],{"emptyLinePlaceholder":309},[115,83701,83702],{"class":117,"line":2104},[115,83703,83704],{},"  backend \"s3\" {\n",[115,83706,83707],{"class":117,"line":2113},[115,83708,83709],{},"    bucket         = \"my-terraform-state\"\n",[115,83711,83712],{"class":117,"line":2122},[115,83713,83714],{},"    key            = \"django-server-baseline\u002Fterraform.tfstate\"\n",[115,83716,83717],{"class":117,"line":2131},[115,83718,83719],{},"    region         = \"us-east-1\"\n",[115,83721,83722],{"class":117,"line":2136},[115,83723,83724],{},"    encrypt        = true\n",[115,83726,83727],{"class":117,"line":2142},[115,83728,83729],{},"    dynamodb_table = \"terraform-locks\"\n",[115,83731,83732],{"class":117,"line":2273},[115,83733,83695],{},[115,83735,83736],{"class":117,"line":2282},[115,83737,2323],{},[115,83739,83740],{"class":117,"line":2291},[115,83741,310],{"emptyLinePlaceholder":309},[115,83743,83744],{"class":117,"line":2299},[115,83745,83746],{},"provider \"digitalocean\" {}\n",[16,83748,83749],{},"Remote state matters because local state is fragile for team or production use. Use a backend with access controls, encryption, and locking. On AWS-backed remote state, an S3 bucket plus DynamoDB locking is a common setup.",[52,83751,83753],{"id":83752},"_4-define-variables","4. Define variables",[16,83755,83756,241],{},[20,83757,83758],{},"variables.tf",[106,83760,83762],{"className":83648,"code":83761,"language":83650,"meta":111,"style":111},"variable \"project_name\" {\n  type = string\n}\n\nvariable \"region\" {\n  type = string\n}\n\nvariable \"size\" {\n  type = string\n}\n\nvariable \"image\" {\n  type = string\n  default = \"ubuntu-22-04-x64\"\n}\n\nvariable \"ssh_key_fingerprints\" {\n  type = list(string)\n}\n\nvariable \"allowed_ssh_cidrs\" {\n  type = list(string)\n}\n\nvariable \"hostname\" {\n  type = string\n}\n",[20,83763,83764,83769,83774,83778,83782,83787,83791,83795,83799,83804,83808,83812,83816,83821,83825,83830,83834,83838,83843,83848,83852,83856,83861,83865,83869,83873,83878,83882],{"__ignoreMap":111},[115,83765,83766],{"class":117,"line":118},[115,83767,83768],{},"variable \"project_name\" {\n",[115,83770,83771],{"class":117,"line":136},[115,83772,83773],{},"  type = string\n",[115,83775,83776],{"class":117,"line":149},[115,83777,2323],{},[115,83779,83780],{"class":117,"line":162},[115,83781,310],{"emptyLinePlaceholder":309},[115,83783,83784],{"class":117,"line":175},[115,83785,83786],{},"variable \"region\" {\n",[115,83788,83789],{"class":117,"line":350},[115,83790,83773],{},[115,83792,83793],{"class":117,"line":365},[115,83794,2323],{},[115,83796,83797],{"class":117,"line":380},[115,83798,310],{"emptyLinePlaceholder":309},[115,83800,83801],{"class":117,"line":487},[115,83802,83803],{},"variable \"size\" {\n",[115,83805,83806],{"class":117,"line":2095},[115,83807,83773],{},[115,83809,83810],{"class":117,"line":2104},[115,83811,2323],{},[115,83813,83814],{"class":117,"line":2113},[115,83815,310],{"emptyLinePlaceholder":309},[115,83817,83818],{"class":117,"line":2122},[115,83819,83820],{},"variable \"image\" {\n",[115,83822,83823],{"class":117,"line":2131},[115,83824,83773],{},[115,83826,83827],{"class":117,"line":2136},[115,83828,83829],{},"  default = \"ubuntu-22-04-x64\"\n",[115,83831,83832],{"class":117,"line":2142},[115,83833,2323],{},[115,83835,83836],{"class":117,"line":2273},[115,83837,310],{"emptyLinePlaceholder":309},[115,83839,83840],{"class":117,"line":2282},[115,83841,83842],{},"variable \"ssh_key_fingerprints\" {\n",[115,83844,83845],{"class":117,"line":2291},[115,83846,83847],{},"  type = list(string)\n",[115,83849,83850],{"class":117,"line":2299},[115,83851,2323],{},[115,83853,83854],{"class":117,"line":2307},[115,83855,310],{"emptyLinePlaceholder":309},[115,83857,83858],{"class":117,"line":2315},[115,83859,83860],{},"variable \"allowed_ssh_cidrs\" {\n",[115,83862,83863],{"class":117,"line":2320},[115,83864,83847],{},[115,83866,83867],{"class":117,"line":7083},[115,83868,2323],{},[115,83870,83871],{"class":117,"line":7090},[115,83872,310],{"emptyLinePlaceholder":309},[115,83874,83875],{"class":117,"line":7097},[115,83876,83877],{},"variable \"hostname\" {\n",[115,83879,83880],{"class":117,"line":7108},[115,83881,83773],{},[115,83883,83884],{"class":117,"line":7113},[115,83885,2323],{},[16,83887,83888,241],{},[20,83889,83537],{},[106,83891,83893],{"className":83648,"code":83892,"language":83650,"meta":111,"style":111},"project_name         = \"myapp-prod\"\nregion               = \"nyc3\"\nsize                 = \"s-1vcpu-1gb\"\nhostname             = \"myapp-web-01\"\nssh_key_fingerprints = [\"ab:cd:ef:12:34:56:78:90:12:34:56:78:90:ab:cd:ef\"]\nallowed_ssh_cidrs    = [\"203.0.113.10\u002F32\"]\n",[20,83894,83895,83900,83905,83910,83915,83920],{"__ignoreMap":111},[115,83896,83897],{"class":117,"line":118},[115,83898,83899],{},"project_name         = \"myapp-prod\"\n",[115,83901,83902],{"class":117,"line":136},[115,83903,83904],{},"region               = \"nyc3\"\n",[115,83906,83907],{"class":117,"line":149},[115,83908,83909],{},"size                 = \"s-1vcpu-1gb\"\n",[115,83911,83912],{"class":117,"line":162},[115,83913,83914],{},"hostname             = \"myapp-web-01\"\n",[115,83916,83917],{"class":117,"line":175},[115,83918,83919],{},"ssh_key_fingerprints = [\"ab:cd:ef:12:34:56:78:90:12:34:56:78:90:ab:cd:ef\"]\n",[115,83921,83922],{"class":117,"line":350},[115,83923,83924],{},"allowed_ssh_cidrs    = [\"203.0.113.10\u002F32\"]\n",[16,83926,83927,83928,83930],{},"Restrict SSH CIDRs to trusted office or home IPs where possible. Avoid ",[20,83929,10348],{}," for SSH in production.",[52,83932,83934],{"id":83933},"_5-provision-the-server-and-firewall","5. Provision the server and firewall",[16,83936,83937,241],{},[20,83938,83939],{},"main.tf",[106,83941,83943],{"className":83648,"code":83942,"language":83650,"meta":111,"style":111},"resource \"digitalocean_droplet\" \"django_server\" {\n  name       = var.hostname\n  region     = var.region\n  size       = var.size\n  image      = var.image\n  ssh_keys   = var.ssh_key_fingerprints\n  user_data  = file(\"${path.module}\u002Fcloud-init.yaml\")\n  monitoring = true\n  tags       = [var.project_name, \"django\", \"baseline\"]\n}\n\nresource \"digitalocean_firewall\" \"django_server_fw\" {\n  name = \"${var.project_name}-fw\"\n\n  droplet_ids = [digitalocean_droplet.django_server.id]\n\n  inbound_rule {\n    protocol         = \"tcp\"\n    port_range       = \"22\"\n    source_addresses = var.allowed_ssh_cidrs\n  }\n\n  inbound_rule {\n    protocol         = \"tcp\"\n    port_range       = \"80\"\n    source_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n  }\n\n  inbound_rule {\n    protocol         = \"tcp\"\n    port_range       = \"443\"\n    source_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n  }\n\n  outbound_rule {\n    protocol              = \"tcp\"\n    port_range            = \"1-65535\"\n    destination_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n  }\n\n  outbound_rule {\n    protocol              = \"udp\"\n    port_range            = \"1-65535\"\n    destination_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n  }\n}\n",[20,83944,83945,83950,83955,83960,83965,83970,83975,83980,83985,83990,83994,83998,84003,84008,84012,84017,84021,84026,84031,84036,84041,84045,84049,84053,84057,84062,84067,84071,84075,84079,84083,84088,84092,84096,84100,84105,84110,84115,84120,84124,84128,84132,84137,84141,84145,84149],{"__ignoreMap":111},[115,83946,83947],{"class":117,"line":118},[115,83948,83949],{},"resource \"digitalocean_droplet\" \"django_server\" {\n",[115,83951,83952],{"class":117,"line":136},[115,83953,83954],{},"  name       = var.hostname\n",[115,83956,83957],{"class":117,"line":149},[115,83958,83959],{},"  region     = var.region\n",[115,83961,83962],{"class":117,"line":162},[115,83963,83964],{},"  size       = var.size\n",[115,83966,83967],{"class":117,"line":175},[115,83968,83969],{},"  image      = var.image\n",[115,83971,83972],{"class":117,"line":350},[115,83973,83974],{},"  ssh_keys   = var.ssh_key_fingerprints\n",[115,83976,83977],{"class":117,"line":365},[115,83978,83979],{},"  user_data  = file(\"${path.module}\u002Fcloud-init.yaml\")\n",[115,83981,83982],{"class":117,"line":380},[115,83983,83984],{},"  monitoring = true\n",[115,83986,83987],{"class":117,"line":487},[115,83988,83989],{},"  tags       = [var.project_name, \"django\", \"baseline\"]\n",[115,83991,83992],{"class":117,"line":2095},[115,83993,2323],{},[115,83995,83996],{"class":117,"line":2104},[115,83997,310],{"emptyLinePlaceholder":309},[115,83999,84000],{"class":117,"line":2113},[115,84001,84002],{},"resource \"digitalocean_firewall\" \"django_server_fw\" {\n",[115,84004,84005],{"class":117,"line":2122},[115,84006,84007],{},"  name = \"${var.project_name}-fw\"\n",[115,84009,84010],{"class":117,"line":2131},[115,84011,310],{"emptyLinePlaceholder":309},[115,84013,84014],{"class":117,"line":2136},[115,84015,84016],{},"  droplet_ids = [digitalocean_droplet.django_server.id]\n",[115,84018,84019],{"class":117,"line":2142},[115,84020,310],{"emptyLinePlaceholder":309},[115,84022,84023],{"class":117,"line":2273},[115,84024,84025],{},"  inbound_rule {\n",[115,84027,84028],{"class":117,"line":2282},[115,84029,84030],{},"    protocol         = \"tcp\"\n",[115,84032,84033],{"class":117,"line":2291},[115,84034,84035],{},"    port_range       = \"22\"\n",[115,84037,84038],{"class":117,"line":2299},[115,84039,84040],{},"    source_addresses = var.allowed_ssh_cidrs\n",[115,84042,84043],{"class":117,"line":2307},[115,84044,83695],{},[115,84046,84047],{"class":117,"line":2315},[115,84048,310],{"emptyLinePlaceholder":309},[115,84050,84051],{"class":117,"line":2320},[115,84052,84025],{},[115,84054,84055],{"class":117,"line":7083},[115,84056,84030],{},[115,84058,84059],{"class":117,"line":7090},[115,84060,84061],{},"    port_range       = \"80\"\n",[115,84063,84064],{"class":117,"line":7097},[115,84065,84066],{},"    source_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n",[115,84068,84069],{"class":117,"line":7108},[115,84070,83695],{},[115,84072,84073],{"class":117,"line":7113},[115,84074,310],{"emptyLinePlaceholder":309},[115,84076,84077],{"class":117,"line":16535},[115,84078,84025],{},[115,84080,84081],{"class":117,"line":16544},[115,84082,84030],{},[115,84084,84085],{"class":117,"line":16549},[115,84086,84087],{},"    port_range       = \"443\"\n",[115,84089,84090],{"class":117,"line":16555},[115,84091,84066],{},[115,84093,84094],{"class":117,"line":16564},[115,84095,83695],{},[115,84097,84098],{"class":117,"line":16573},[115,84099,310],{"emptyLinePlaceholder":309},[115,84101,84102],{"class":117,"line":16582},[115,84103,84104],{},"  outbound_rule {\n",[115,84106,84107],{"class":117,"line":16587},[115,84108,84109],{},"    protocol              = \"tcp\"\n",[115,84111,84112],{"class":117,"line":16596},[115,84113,84114],{},"    port_range            = \"1-65535\"\n",[115,84116,84117],{"class":117,"line":16609},[115,84118,84119],{},"    destination_addresses = [\"0.0.0.0\u002F0\", \"::\u002F0\"]\n",[115,84121,84122],{"class":117,"line":16614},[115,84123,83695],{},[115,84125,84126],{"class":117,"line":16624},[115,84127,310],{"emptyLinePlaceholder":309},[115,84129,84130],{"class":117,"line":16632},[115,84131,84104],{},[115,84133,84134],{"class":117,"line":16640},[115,84135,84136],{},"    protocol              = \"udp\"\n",[115,84138,84139],{"class":117,"line":16646},[115,84140,84114],{},[115,84142,84143],{"class":117,"line":16651},[115,84144,84119],{},[115,84146,84147],{"class":117,"line":16656},[115,84148,83695],{},[115,84150,84151],{"class":117,"line":16666},[115,84152,2323],{},[16,84154,84155,84156,84159],{},"This example uses the ",[1226,84157,84158],{},"provider firewall"," as the baseline network control. The outbound policy is intentionally permissive, which is common for general-purpose app servers.",[16,84161,84162,84163,3146,84165,84167],{},"If you do not plan to install a reverse proxy immediately, omit ports ",[20,84164,3808],{},[20,84166,2174],{}," until needed.",[16,84169,84170],{},"If stable DNS cutovers matter, add a reserved IP rather than relying on an ephemeral server IP.",[52,84172,84174],{"id":84173},"_6-bootstrap-safely-with-cloud-init","6. Bootstrap safely with cloud-init",[16,84176,84177,84178,84181],{},"Terraform is provisioning the server, while ",[1226,84179,84180],{},"cloud-init handles first-boot configuration"," inside the instance. Keep that boundary clear: Terraform manages infrastructure state; cloud-init handles initial OS bootstrap.",[16,84183,84184,241],{},[20,84185,84186],{},"cloud-init.yaml",[106,84188,84190],{"className":2485,"code":84189,"language":2487,"meta":111,"style":111},"#cloud-config\npackage_update: true\npackage_upgrade: true\n\nusers:\n  - default\n  - name: deploy\n    groups: sudo\n    shell: \u002Fbin\u002Fbash\n    sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n    ssh_authorized_keys:\n      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...replace-with-your-public-key\n\nwrite_files:\n  - path: \u002Fetc\u002Fssh\u002Fsshd_config.d\u002F99-hardening.conf\n    permissions: '0644'\n    content: |\n      PasswordAuthentication no\n      KbdInteractiveAuthentication no\n      PubkeyAuthentication yes\n      PermitRootLogin no\n\npackages:\n  - fail2ban\n  - unattended-upgrades\n  - apt-listchanges\n  - ca-certificates\n  - curl\n  - git\n  - vim\n  - htop\n\nruncmd:\n  - timedatectl set-timezone UTC\n  - sshd -t\n  - systemctl restart ssh\n  - systemctl enable fail2ban\n  - systemctl start fail2ban\n  - dpkg-reconfigure -f noninteractive unattended-upgrades\n",[20,84191,84192,84197,84206,84215,84219,84226,84233,84244,84254,84264,84275,84282,84289,84293,84300,84312,84322,84331,84336,84341,84346,84351,84355,84362,84369,84376,84383,84390,84397,84403,84410,84416,84420,84427,84434,84441,84448,84455,84462],{"__ignoreMap":111},[115,84193,84194],{"class":117,"line":118},[115,84195,84196],{"class":3861},"#cloud-config\n",[115,84198,84199,84202,84204],{"class":117,"line":136},[115,84200,84201],{"class":2494},"package_update",[115,84203,2513],{"class":125},[115,84205,20805],{"class":202},[115,84207,84208,84211,84213],{"class":117,"line":149},[115,84209,84210],{"class":2494},"package_upgrade",[115,84212,2513],{"class":125},[115,84214,20805],{"class":202},[115,84216,84217],{"class":117,"line":162},[115,84218,310],{"emptyLinePlaceholder":309},[115,84220,84221,84224],{"class":117,"line":175},[115,84222,84223],{"class":2494},"users",[115,84225,2498],{"class":125},[115,84227,84228,84230],{"class":117,"line":350},[115,84229,20762],{"class":125},[115,84231,84232],{"class":132},"default\n",[115,84234,84235,84237,84239,84241],{"class":117,"line":365},[115,84236,20762],{"class":125},[115,84238,20820],{"class":2494},[115,84240,2513],{"class":125},[115,84242,84243],{"class":132},"deploy\n",[115,84245,84246,84249,84251],{"class":117,"line":380},[115,84247,84248],{"class":2494},"    groups",[115,84250,2513],{"class":125},[115,84252,84253],{"class":132},"sudo\n",[115,84255,84256,84259,84261],{"class":117,"line":487},[115,84257,84258],{"class":2494},"    shell",[115,84260,2513],{"class":125},[115,84262,84263],{"class":132},"\u002Fbin\u002Fbash\n",[115,84265,84266,84268,84270,84273],{"class":117,"line":2095},[115,84267,80894],{"class":2494},[115,84269,2541],{"class":125},[115,84271,84272],{"class":132},"\"ALL=(ALL) NOPASSWD:ALL\"",[115,84274,2552],{"class":125},[115,84276,84277,84280],{"class":117,"line":2104},[115,84278,84279],{"class":2494},"    ssh_authorized_keys",[115,84281,2498],{"class":125},[115,84283,84284,84286],{"class":117,"line":2113},[115,84285,5976],{"class":125},[115,84287,84288],{"class":132},"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...replace-with-your-public-key\n",[115,84290,84291],{"class":117,"line":2122},[115,84292,310],{"emptyLinePlaceholder":309},[115,84294,84295,84298],{"class":117,"line":2131},[115,84296,84297],{"class":2494},"write_files",[115,84299,2498],{"class":125},[115,84301,84302,84304,84307,84309],{"class":117,"line":2136},[115,84303,20762],{"class":125},[115,84305,84306],{"class":2494},"path",[115,84308,2513],{"class":125},[115,84310,84311],{"class":132},"\u002Fetc\u002Fssh\u002Fsshd_config.d\u002F99-hardening.conf\n",[115,84313,84314,84317,84319],{"class":117,"line":2142},[115,84315,84316],{"class":2494},"    permissions",[115,84318,2513],{"class":125},[115,84320,84321],{"class":132},"'0644'\n",[115,84323,84324,84327,84329],{"class":117,"line":2273},[115,84325,84326],{"class":2494},"    content",[115,84328,2513],{"class":125},[115,84330,27947],{"class":121},[115,84332,84333],{"class":117,"line":2282},[115,84334,84335],{"class":132},"      PasswordAuthentication no\n",[115,84337,84338],{"class":117,"line":2291},[115,84339,84340],{"class":132},"      KbdInteractiveAuthentication no\n",[115,84342,84343],{"class":117,"line":2299},[115,84344,84345],{"class":132},"      PubkeyAuthentication yes\n",[115,84347,84348],{"class":117,"line":2307},[115,84349,84350],{"class":132},"      PermitRootLogin no\n",[115,84352,84353],{"class":117,"line":2315},[115,84354,310],{"emptyLinePlaceholder":309},[115,84356,84357,84360],{"class":117,"line":2320},[115,84358,84359],{"class":2494},"packages",[115,84361,2498],{"class":125},[115,84363,84364,84366],{"class":117,"line":7083},[115,84365,20762],{"class":125},[115,84367,84368],{"class":132},"fail2ban\n",[115,84370,84371,84373],{"class":117,"line":7090},[115,84372,20762],{"class":125},[115,84374,84375],{"class":132},"unattended-upgrades\n",[115,84377,84378,84380],{"class":117,"line":7097},[115,84379,20762],{"class":125},[115,84381,84382],{"class":132},"apt-listchanges\n",[115,84384,84385,84387],{"class":117,"line":7108},[115,84386,20762],{"class":125},[115,84388,84389],{"class":132},"ca-certificates\n",[115,84391,84392,84394],{"class":117,"line":7113},[115,84393,20762],{"class":125},[115,84395,84396],{"class":132},"curl\n",[115,84398,84399,84401],{"class":117,"line":16535},[115,84400,20762],{"class":125},[115,84402,20888],{"class":132},[115,84404,84405,84407],{"class":117,"line":16544},[115,84406,20762],{"class":125},[115,84408,84409],{"class":132},"vim\n",[115,84411,84412,84414],{"class":117,"line":16549},[115,84413,20762],{"class":125},[115,84415,70445],{"class":132},[115,84417,84418],{"class":117,"line":16555},[115,84419,310],{"emptyLinePlaceholder":309},[115,84421,84422,84425],{"class":117,"line":16564},[115,84423,84424],{"class":2494},"runcmd",[115,84426,2498],{"class":125},[115,84428,84429,84431],{"class":117,"line":16573},[115,84430,20762],{"class":125},[115,84432,84433],{"class":132},"timedatectl set-timezone UTC\n",[115,84435,84436,84438],{"class":117,"line":16582},[115,84437,20762],{"class":125},[115,84439,84440],{"class":132},"sshd -t\n",[115,84442,84443,84445],{"class":117,"line":16587},[115,84444,20762],{"class":125},[115,84446,84447],{"class":132},"systemctl restart ssh\n",[115,84449,84450,84452],{"class":117,"line":16596},[115,84451,20762],{"class":125},[115,84453,84454],{"class":132},"systemctl enable fail2ban\n",[115,84456,84457,84459],{"class":117,"line":16609},[115,84458,20762],{"class":125},[115,84460,84461],{"class":132},"systemctl start fail2ban\n",[115,84463,84464,84466],{"class":117,"line":16614},[115,84465,20762],{"class":125},[115,84467,84468],{"class":132},"dpkg-reconfigure -f noninteractive unattended-upgrades\n",[16,84470,8508],{},[63,84472,84473,84479,84485,84488,84491,84501],{},[66,84474,84475,84478],{},[20,84476,84477],{},"NOPASSWD:ALL"," is convenient for automation and bootstrap access, but it is a broad privilege grant. Tighten sudo policy later if your team does not need that level of access.",[66,84480,84481,84482,84484],{},"This example relies on the ",[1226,84483,84158],{},", so it does not install or enable UFW. If you want both provider-level and host-level firewalling, configure host firewall rules explicitly instead of just installing the package.",[66,84486,84487],{},"If you install Docker, Nginx, or Python build packages here, keep that baseline-oriented.",[66,84489,84490],{},"Avoid embedding Django settings, database passwords, or application secrets in cloud-init.",[66,84492,84493,84494,84497,84498,84500],{},"Cloud-init is mainly a ",[1226,84495,84496],{},"first-boot"," mechanism. Editing ",[20,84499,84186],{}," later does not mean those changes will automatically apply to existing servers.",[66,84502,84503],{},"If you need repeatable in-place configuration changes across existing hosts, use cloud-init for initial access and hand off to Ansible or a deploy pipeline.",[52,84505,84507],{"id":84506},"_7-add-useful-outputs","7. Add useful outputs",[16,84509,84510,241],{},[20,84511,84512],{},"outputs.tf",[106,84514,84516],{"className":83648,"code":84515,"language":83650,"meta":111,"style":111},"output \"server_ip\" {\n  value = digitalocean_droplet.django_server.ipv4_address\n}\n\noutput \"server_name\" {\n  value = digitalocean_droplet.django_server.name\n}\n\noutput \"ssh_command\" {\n  value = \"ssh deploy@${digitalocean_droplet.django_server.ipv4_address}\"\n}\n",[20,84517,84518,84523,84528,84532,84536,84541,84546,84550,84554,84559,84564],{"__ignoreMap":111},[115,84519,84520],{"class":117,"line":118},[115,84521,84522],{},"output \"server_ip\" {\n",[115,84524,84525],{"class":117,"line":136},[115,84526,84527],{},"  value = digitalocean_droplet.django_server.ipv4_address\n",[115,84529,84530],{"class":117,"line":149},[115,84531,2323],{},[115,84533,84534],{"class":117,"line":162},[115,84535,310],{"emptyLinePlaceholder":309},[115,84537,84538],{"class":117,"line":175},[115,84539,84540],{},"output \"server_name\" {\n",[115,84542,84543],{"class":117,"line":350},[115,84544,84545],{},"  value = digitalocean_droplet.django_server.name\n",[115,84547,84548],{"class":117,"line":365},[115,84549,2323],{},[115,84551,84552],{"class":117,"line":380},[115,84553,310],{"emptyLinePlaceholder":309},[115,84555,84556],{"class":117,"line":487},[115,84557,84558],{},"output \"ssh_command\" {\n",[115,84560,84561],{"class":117,"line":2095},[115,84562,84563],{},"  value = \"ssh deploy@${digitalocean_droplet.django_server.ipv4_address}\"\n",[115,84565,84566],{"class":117,"line":2104},[115,84567,2323],{},[52,84569,84571],{"id":84570},"_8-validate-plan-and-apply","8. Validate, plan, and apply",[16,84573,33361],{},[106,84575,84577],{"className":108,"code":84576,"language":110,"meta":111,"style":111},"terraform init\nterraform fmt\nterraform validate\nterraform plan -out=tfplan\nterraform apply tfplan\n",[20,84578,84579,84587,84594,84601,84611],{"__ignoreMap":111},[115,84580,84581,84584],{"class":117,"line":118},[115,84582,84583],{"class":262},"terraform",[115,84585,84586],{"class":132}," init\n",[115,84588,84589,84591],{"class":117,"line":136},[115,84590,84583],{"class":262},[115,84592,84593],{"class":132}," fmt\n",[115,84595,84596,84598],{"class":117,"line":149},[115,84597,84583],{"class":262},[115,84599,84600],{"class":132}," validate\n",[115,84602,84603,84605,84608],{"class":117,"line":162},[115,84604,84583],{"class":262},[115,84606,84607],{"class":132}," plan",[115,84609,84610],{"class":202}," -out=tfplan\n",[115,84612,84613,84615,84618],{"class":117,"line":175},[115,84614,84583],{"class":262},[115,84616,84617],{"class":132}," apply",[115,84619,84620],{"class":132}," tfplan\n",[16,84622,84623],{},"Before applying, review the plan for:",[63,84625,84626,84629,84632,84635],{},[66,84627,84628],{},"unexpected resource replacement",[66,84630,84631],{},"overly broad firewall exposure",[66,84633,84634],{},"the correct image, size, and region",[66,84636,84637,84638,84640],{},"the expected ",[20,84639,84186],{}," reference",[52,84642,84644],{"id":84643},"_9-verify-the-baseline-before-deploying-django","9. Verify the baseline before deploying Django",[16,84646,84647],{},"Get outputs:",[106,84649,84651],{"className":108,"code":84650,"language":110,"meta":111,"style":111},"terraform output\n",[20,84652,84653],{"__ignoreMap":111},[115,84654,84655,84657],{"class":117,"line":118},[115,84656,84583],{"class":262},[115,84658,84659],{"class":132}," output\n",[16,84661,84662],{},"Connect by SSH:",[106,84664,84666],{"className":108,"code":84665,"language":110,"meta":111,"style":111},"ssh deploy@server-ip\n",[20,84667,84668],{"__ignoreMap":111},[115,84669,84670,84672],{"class":117,"line":118},[115,84671,14184],{"class":262},[115,84673,84674],{"class":132}," deploy@server-ip\n",[16,84676,84677],{},"Run baseline checks:",[106,84679,84681],{"className":108,"code":84680,"language":110,"meta":111,"style":111},"whoami\nlsb_release -a\ncloud-init status --wait\nsystemctl status ssh\nsystemctl status fail2ban\nsudo sshd -t\ntail -n 100 \u002Fvar\u002Flog\u002Fcloud-init-output.log\nsudo ss -tulpn\nsudo ip6tables -L -n 2>\u002Fdev\u002Fnull || true\n",[20,84682,84683,84688,84695,84704,84712,84720,84729,84740,84748],{"__ignoreMap":111},[115,84684,84685],{"class":117,"line":118},[115,84686,84687],{"class":262},"whoami\n",[115,84689,84690,84693],{"class":117,"line":136},[115,84691,84692],{"class":262},"lsb_release",[115,84694,206],{"class":202},[115,84696,84697,84700,84702],{"class":117,"line":149},[115,84698,84699],{"class":262},"cloud-init",[115,84701,1984],{"class":132},[115,84703,19714],{"class":202},[115,84705,84706,84708,84710],{"class":117,"line":162},[115,84707,1981],{"class":262},[115,84709,1984],{"class":132},[115,84711,14418],{"class":132},[115,84713,84714,84716,84718],{"class":117,"line":175},[115,84715,1981],{"class":262},[115,84717,1984],{"class":132},[115,84719,14332],{"class":132},[115,84721,84722,84724,84727],{"class":117,"line":350},[115,84723,2001],{"class":262},[115,84725,84726],{"class":132}," sshd",[115,84728,4282],{"class":202},[115,84730,84731,84733,84735,84737],{"class":117,"line":365},[115,84732,36495],{"class":262},[115,84734,2794],{"class":202},[115,84736,2797],{"class":202},[115,84738,84739],{"class":132}," \u002Fvar\u002Flog\u002Fcloud-init-output.log\n",[115,84741,84742,84744,84746],{"class":117,"line":380},[115,84743,2001],{"class":262},[115,84745,2004],{"class":132},[115,84747,2007],{"class":202},[115,84749,84750,84752,84755,84758,84760,84762,84765,84767],{"class":117,"line":487},[115,84751,2001],{"class":262},[115,84753,84754],{"class":132}," ip6tables",[115,84756,84757],{"class":202}," -L",[115,84759,2794],{"class":202},[115,84761,37537],{"class":121},[115,84763,84764],{"class":132},"\u002Fdev\u002Fnull",[115,84766,43235],{"class":121},[115,84768,67682],{"class":202},[16,84770,68238],{},[63,84772,84773,84778,84781,84784,84787,84793,84796],{},[66,84774,84775,84776],{},"login works as ",[20,84777,3098],{},[66,84779,84780],{},"the correct Ubuntu version is installed",[66,84782,84783],{},"cloud-init completed and did not leave obvious errors in logs",[66,84785,84786],{},"SSH config validates cleanly",[66,84788,84789,84792],{},[20,84790,84791],{},"fail2ban"," is running",[66,84794,84795],{},"only expected ports are listening",[66,84797,84798],{},"IPv6 exposure matches your expected network policy",[16,84800,84801],{},"Also verify firewall behavior at the right layer:",[63,84803,84804,84810],{},[66,84805,84806,84809],{},[1226,84807,84808],{},"Provider firewall:"," check the Terraform configuration and provider console or CLI",[66,84811,84812,84815],{},[1226,84813,84814],{},"Host firewall:"," only verify this if you explicitly configured one",[11,84817,1321],{"id":1320},[16,84819,84820,84821,54269,84824,211],{},"This setup works because it separates ",[1226,84822,84823],{},"baseline provisioning",[1226,84825,84826],{},"application deployment",[16,84828,84829],{},"Terraform is good at managing infrastructure resources and their desired state:",[63,84831,84832,84835,84838,84841,84844],{},[66,84833,84834],{},"server creation",[66,84836,84837],{},"firewall attachment",[66,84839,84840],{},"reserved IPs",[66,84842,84843],{},"tags and metadata",[66,84845,84846],{},"bootstrap file delivery",[16,84848,84849],{},"Cloud-init is good for first-boot initialization:",[63,84851,84852,84855,84858,84861],{},[66,84853,84854],{},"creating users",[66,84856,84857],{},"setting SSH policy",[66,84859,84860],{},"installing baseline packages",[66,84862,84863],{},"enabling services",[16,84865,84866],{},"That separation keeps Terraform focused on infrastructure and avoids turning it into a full application deploy tool. In a Django workflow, that usually leads to fewer surprises. Your app release process can then be handled by CI\u002FCD, Ansible, shell scripts, or a container workflow.",[16,84868,84869,84870,4493,84873,84875],{},"Be careful with change types. Updating tags or firewall rules is usually low risk. Changing image IDs, some bootstrap settings, or instance-level properties can force recreation depending on provider behavior. Bootstrap changes are especially important here: changing ",[20,84871,84872],{},"user_data",[20,84874,84186],{}," does not give you a safe in-place rollback model, because cloud-init usually does not re-run fully on an existing instance.",[16,84877,84878],{},"That is why durable state should live outside the server where possible:",[63,84880,84881,84884,84887],{},[66,84882,84883],{},"PostgreSQL on a managed service or separate host",[66,84885,84886],{},"Redis on a dedicated service or node",[66,84888,84889],{},"user-uploaded media on object storage",[16,84891,84892],{},"If the app server is disposable, rebuilding from Terraform is safer and faster.",[52,84894,84896],{"id":84895},"when-this-manual-setup-becomes-repetitive","When this manual setup becomes repetitive",[16,84898,84899],{},"Once you are provisioning more than one Django environment, this baseline should become a reusable module and a standard cloud-init template. The first pieces worth automating are provider inputs, bootstrap rendering, and post-provision validation checks. That keeps environments consistent without mixing app release logic into the infrastructure baseline.",[11,84901,1337],{"id":1336},[63,84903,84904,84909,84914,84926,84935],{},[66,84905,84906,84908],{},[1226,84907,41808],{}," Do not assume they belong in Terraform. Static collection and media storage are app deployment concerns, often handled by object storage or the app release pipeline.",[66,84910,84911,84913],{},[1226,84912,2926],{}," Terraform should not run Django migrations. That belongs in your deploy workflow after the server baseline is verified.",[66,84915,84916,84919,84920,1153,84922,20346,84924,34606],{},[1226,84917,84918],{},"Proxy and TLS headers:"," If this host will later run Nginx or Caddy in front of Gunicorn or Uvicorn, make sure your future Django settings handle ",[20,84921,2377],{},[20,84923,2719],{},[20,84925,2725],{},[66,84927,84928,84931,84932,84934],{},[1226,84929,84930],{},"User data changes:"," Updating ",[20,84933,84186],{}," does not necessarily re-run setup on an existing server. Treat bootstrap edits as affecting new hosts unless you intentionally manage in-place changes another way.",[66,84936,84937,84940,84941,84944],{},[1226,84938,84939],{},"Rollback and recovery:"," If a Terraform change breaks access, first revert the change in version control and inspect ",[20,84942,84943],{},"terraform plan"," carefully. If SSH is lost, use provider console or rescue access. For broken bootstrap changes, rebuilding a fresh host from the last known-good baseline is often safer than trying to repair a drifted server manually.",[11,84946,1386],{"id":1385},[16,84948,84949,84950,211],{},"For the infrastructure boundary, start with ",[1395,84951,84952],{"href":71135},"What Terraform Should Manage in a Django Deployment",[16,84954,84955,84956,211],{},"After the baseline exists, the next app-layer step is usually ",[1395,84957,2986],{"href":2985},[16,84959,84960,84961,211],{},"If your stack will use separate services, continue with ",[1395,84962,84963],{"href":1403},"Set Up PostgreSQL and Redis for Django Production",[16,84965,84966,84967,211],{},"If provisioning fails or SSH access is broken, use ",[1395,84968,84969],{"href":22931},"How to Troubleshoot Django Server Provisioning and SSH Access Issues",[11,84971,1420],{"id":1419},[52,84973,84975],{"id":84974},"should-terraform-install-django-and-deploy-application-code-too","Should Terraform install Django and deploy application code too?",[16,84977,84978],{},"Usually no. Terraform should provision infrastructure baseline, not act as your app release system. Keep Django code deploys, migrations, static collection, and restarts in CI\u002FCD, Ansible, or release scripts.",[52,84980,84982],{"id":84981},"is-cloud-init-enough-for-server-bootstrap-or-should-i-use-ansible-as-well","Is cloud-init enough for server bootstrap, or should I use Ansible as well?",[16,84984,84985],{},"Cloud-init is enough for first-boot baseline setup on many small deployments. If you need repeatable in-place configuration changes across existing hosts, Ansible is often a better fit.",[52,84987,84989],{"id":84988},"how-do-i-keep-secrets-out-of-terraform-state-when-provisioning-a-django-server","How do I keep secrets out of Terraform state when provisioning a Django server?",[16,84991,84992,84993,84995],{},"Do not put app secrets, database passwords, or ",[20,84994,191],{}," contents into Terraform variables unless you fully understand the state exposure risk. Use provider credentials via environment variables, and inject app secrets later through a secret manager, CI\u002FCD, or host-level secret distribution.",[52,84997,84999],{"id":84998},"what-terraform-changes-are-most-likely-to-recreate-the-server","What Terraform changes are most likely to recreate the server?",[16,85001,85002,85003,55918,85005,85008],{},"Image changes, some instance configuration changes, and provider-specific immutable fields are common triggers. Always inspect ",[20,85004,84943],{},[20,85006,85007],{},"replace"," actions before applying.",[52,85010,85012],{"id":85011},"should-postgresql-run-on-the-same-terraform-provisioned-server-as-django","Should PostgreSQL run on the same Terraform-provisioned server as Django?",[16,85014,85015],{},"For small projects it can, but it increases coupling and makes rebuilds harder. For production reliability, keeping PostgreSQL and other durable services outside the app server is usually the safer baseline.",[1485,85017,85018],{},"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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":111,"searchDepth":149,"depth":149,"links":85020},[85021,85022,85023,85034,85037,85038,85039],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43,"children":85024},[85025,85026,85027,85028,85029,85030,85031,85032,85033],{"id":83465,"depth":149,"text":83466},{"id":83521,"depth":149,"text":83522},{"id":83633,"depth":149,"text":83634},{"id":83752,"depth":149,"text":83753},{"id":83933,"depth":149,"text":83934},{"id":84173,"depth":149,"text":84174},{"id":84506,"depth":149,"text":84507},{"id":84570,"depth":149,"text":84571},{"id":84643,"depth":149,"text":84644},{"id":1320,"depth":136,"text":1321,"children":85035},[85036],{"id":84895,"depth":149,"text":84896},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":85040},[85041,85042,85043,85044,85045],{"id":84974,"depth":149,"text":84975},{"id":84981,"depth":149,"text":84982},{"id":84988,"depth":149,"text":84989},{"id":84998,"depth":149,"text":84999},{"id":85011,"depth":149,"text":85012},"Manual server creation is one of the fastest ways to introduce deployment drift into a Django environment.",{},"\u002Fterraform-django-server-baseline",[37876,1415,73725],{"title":83425,"description":85046},[1557,84583,3101],"terraform-django-server-baseline",[1557,84583,3101],"o1cLydqhuWdkMI_8dguB_74y80vMMdbLJoJzHATGTcg",{"id":85056,"title":85057,"body":85058,"category":3088,"description":86637,"difficulty":1543,"extension":1544,"funnel_stage":1545,"intent":1546,"meta":86638,"navigation":309,"path":86639,"priority":86640,"related":86641,"role":1553,"section":3098,"seo":86642,"stack":86643,"stem":86644,"tags":86645,"type":1561,"__hash__":86646},"articles\u002Frun-celery-with-systemd-django.md","Run Celery Workers with systemd for Django",{"type":8,"value":85059,"toc":86582},[85060,85062,85065,85068,85090,85095,85097,85103,85105,85131,85142,85145,85147,85151,85154,85158,85161,85170,85176,85195,85201,85205,85208,85228,85231,85236,85242,85246,85249,85275,85278,85284,85286,85358,85362,85366,85369,85373,85378,85503,85506,85523,85526,85538,85542,85545,85603,85607,85648,85651,85654,85658,85661,85686,85692,85696,85704,85707,85717,85721,85725,85737,85741,85756,85760,85785,85788,85794,85797,85810,85814,85831,85834,85848,85854,85858,85861,85865,85868,85887,85890,85912,85916,85919,85935,85938,85942,85945,85963,85969,85984,85988,85992,85998,86002,86007,86122,86124,86158,86162,86165,86169,86173,86176,86191,86194,86209,86221,86225,86228,86243,86251,86255,86258,86261,86276,86282,86284,86290,86310,86313,86322,86324,86331,86335,86372,86374,86377,86415,86418,86420,86458,86460,86466,86472,86477,86483,86485,86489,86492,86506,86509,86516,86519,86525,86539,86543,86546,86560,86566,86573,86579],[11,85061,14],{"id":13},[16,85063,85064],{},"Running Celery workers manually in production is fragile. If you start a worker from a shell and disconnect, the process may stop. Even when it stays running, it often depends on shell-specific environment variables, the current working directory, or the active virtual environment.",[16,85066,85067],{},"Common production failures look like this:",[63,85069,85070,85073,85076,85081,85084,85087],{},[66,85071,85072],{},"the worker dies after logout or reboot",[66,85074,85075],{},"Celery starts with the wrong Django settings",[66,85077,85078,85079],{},"the broker URL is missing under ",[20,85080,1277],{},[66,85082,85083],{},"logs are split across shell history, files, and nowhere useful",[66,85085,85086],{},"the worker does not restart after a crash",[66,85088,85089],{},"deployments update code, but the worker keeps running old code",[16,85091,85092,85093,211],{},"For a Django production setup, Celery should be managed like any other long-running service. On most Linux servers, that means ",[20,85094,1277],{},[11,85096,30],{"id":29},[16,85098,85099,85100,85102],{},"Use a dedicated ",[20,85101,1277],{}," unit to run Celery as your app user, not as root. Point it at the correct release directory and virtualenv, load environment variables from a protected env file, and define restart behavior that matches your task runtime.",[16,85104,5144],{},[1173,85106,85107,85112,85115,85118,85123,85126],{},[66,85108,85109,85110],{},"reload ",[20,85111,1277],{},[66,85113,85114],{},"enable the service on boot",[66,85116,85117],{},"start it",[66,85119,85120,85121],{},"verify it with ",[20,85122,23834],{},[66,85124,85125],{},"queue a real test task",[66,85127,85128,85129],{},"read logs with ",[20,85130,2785],{},[16,85132,85133,85134,85137,85138,85141],{},"For most hosts, use ",[20,85135,85136],{},"network-online.target"," instead of only ",[20,85139,85140],{},"network.target"," so the worker does not race broker connectivity during boot.",[16,85143,85144],{},"If a change breaks the worker, roll back by restoring the previous release or the previous unit file, then restart the service and verify task consumption.",[11,85146,43],{"id":42},[11,85148,85150],{"id":85149},"step-1-confirm-the-django-and-celery-production-setup","Step 1 - Confirm the Django and Celery production setup",[16,85152,85153],{},"Before creating a unit file, confirm the pieces Celery needs already work.",[52,85155,85157],{"id":85156},"verify-the-celery-app-entrypoint","Verify the Celery app entrypoint",[16,85159,85160],{},"In most Django projects, Celery is started with an app path like:",[106,85162,85164],{"className":108,"code":85163,"language":110,"meta":111,"style":111},"yourproject.celery:app\n",[20,85165,85166],{"__ignoreMap":111},[115,85167,85168],{"class":117,"line":118},[115,85169,85163],{"class":262},[16,85171,85172,85173,85175],{},"A typical ",[20,85174,2107],{}," uses:",[106,85177,85179],{"className":108,"code":85178,"language":110,"meta":111,"style":111},"celery -A yourproject worker --loglevel=INFO\n",[20,85180,85181],{"__ignoreMap":111},[115,85182,85183,85185,85187,85190,85192],{"class":117,"line":118},[115,85184,5319],{"class":262},[115,85186,5322],{"class":202},[115,85188,85189],{"class":132}," yourproject",[115,85191,4801],{"class":132},[115,85193,85194],{"class":202}," --loglevel=INFO\n",[16,85196,12356,85197,85200],{},[20,85198,85199],{},"yourproject"," matches the Python import path of your Django project.",[52,85202,85204],{"id":85203},"verify-broker-connectivity","Verify broker connectivity",[16,85206,85207],{},"Confirm Redis or RabbitMQ is reachable from the server. For example, if you use Redis:",[106,85209,85211],{"className":108,"code":85210,"language":110,"meta":111,"style":111},"redis-cli -u \"$CELERY_BROKER_URL\" ping\n",[20,85212,85213],{"__ignoreMap":111},[115,85214,85215,85217,85219,85221,85224,85226],{"class":117,"line":118},[115,85216,5375],{"class":262},[115,85218,2788],{"class":202},[115,85220,325],{"class":132},[115,85222,85223],{"class":125},"$CELERY_BROKER_URL",[115,85225,331],{"class":132},[115,85227,5387],{"class":132},[16,85229,85230],{},"You should get:",[106,85232,85234],{"className":85233,"code":8596,"language":247,"meta":111},[245],[20,85235,8596],{"__ignoreMap":111},[16,85237,85238,85239,85241],{},"If broker access is blocked by firewall rules, bad credentials, or the wrong hostname, ",[20,85240,1277],{}," will not fix that.",[52,85243,85245],{"id":85244},"verify-the-runtime-user-and-paths","Verify the runtime user and paths",[16,85247,85248],{},"Decide exactly what the service will use:",[63,85250,85251,85255,85260,85265,85270],{},[66,85252,57331,85253],{},[20,85254,1557],{},[66,85256,85257,85258],{},"app group: ",[20,85259,1557],{},[66,85261,85262,85263],{},"project path: ",[20,85264,14814],{},[66,85266,85267,85268],{},"virtualenv path: ",[20,85269,23122],{},[66,85271,85272,85273],{},"env file: ",[20,85274,82528],{},[16,85276,85277],{},"If you use release directories, a common layout is:",[106,85279,85282],{"className":85280,"code":85281,"language":247,"meta":111},[245],"\u002Fsrv\u002Fmyapp\u002F\n├── current -> \u002Fsrv\u002Fmyapp\u002Freleases\u002F2026-04-24-120000\n├── releases\u002F\n└── venv\u002F\n",[20,85283,85281],{"__ignoreMap":111},[16,85285,3515],{},[106,85287,85289],{"className":108,"code":85288,"language":110,"meta":111,"style":111},"sudo -u django test -x \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery && echo ok\nsudo -u django test -d \u002Fsrv\u002Fmyapp\u002Fcurrent && echo ok\nsudo -u django test -r \u002Fetc\u002Fmyapp\u002Fmyapp.env && echo ok\n",[20,85290,85291,85316,85337],{"__ignoreMap":111},[115,85292,85293,85295,85297,85300,85303,85306,85309,85311,85313],{"class":117,"line":118},[115,85294,2001],{"class":262},[115,85296,2788],{"class":202},[115,85298,85299],{"class":132}," django",[115,85301,85302],{"class":132}," test",[115,85304,85305],{"class":202}," -x",[115,85307,85308],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery",[115,85310,3912],{"class":125},[115,85312,1085],{"class":202},[115,85314,85315],{"class":132}," ok\n",[115,85317,85318,85320,85322,85324,85326,85328,85331,85333,85335],{"class":117,"line":136},[115,85319,2001],{"class":262},[115,85321,2788],{"class":202},[115,85323,85299],{"class":132},[115,85325,85302],{"class":132},[115,85327,1019],{"class":202},[115,85329,85330],{"class":132}," \u002Fsrv\u002Fmyapp\u002Fcurrent",[115,85332,3912],{"class":125},[115,85334,1085],{"class":202},[115,85336,85315],{"class":132},[115,85338,85339,85341,85343,85345,85347,85349,85352,85354,85356],{"class":117,"line":149},[115,85340,2001],{"class":262},[115,85342,2788],{"class":202},[115,85344,85299],{"class":132},[115,85346,85302],{"class":132},[115,85348,12350],{"class":202},[115,85350,85351],{"class":132}," \u002Fetc\u002Fmyapp\u002Fmyapp.env",[115,85353,3912],{"class":125},[115,85355,1085],{"class":202},[115,85357,85315],{"class":132},[11,85359,85361],{"id":85360},"step-2-create-a-dedicated-systemd-service-for-the-celery-worker","Step 2 - Create a dedicated systemd service for the Celery worker",[52,85363,85365],{"id":85364},"choose-the-service-user-and-group","Choose the service user and group",[16,85367,85368],{},"Do not run Celery as root. Use the same non-root user that owns or operates the Django app.",[52,85370,85372],{"id":85371},"define-the-working-directory-and-executable","Define the working directory and executable",[16,85374,8628,85375,241],{},[20,85376,85377],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcelery.service",[106,85379,85381],{"className":2026,"code":85380,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Celery Worker for Django app\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=django\nGroup=django\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A yourproject worker --loglevel=INFO\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=300\nKillMode=control-group\nNoNewPrivileges=true\n\n[Install]\nWantedBy=multi-user.target\n",[20,85382,85383,85387,85394,85402,85408,85412,85416,85422,85428,85434,85440,85446,85457,85463,85469,85476,85483,85489,85493,85497],{"__ignoreMap":111},[115,85384,85385],{"class":117,"line":118},[115,85386,2035],{"class":262},[115,85388,85389,85391],{"class":117,"line":136},[115,85390,2040],{"class":121},[115,85392,85393],{"class":125},"=Celery Worker for Django app\n",[115,85395,85396,85399],{"class":117,"line":149},[115,85397,85398],{"class":121},"Wants",[115,85400,85401],{"class":125},"=network-online.target\n",[115,85403,85404,85406],{"class":117,"line":162},[115,85405,2048],{"class":121},[115,85407,85401],{"class":125},[115,85409,85410],{"class":117,"line":175},[115,85411,310],{"emptyLinePlaceholder":309},[115,85413,85414],{"class":117,"line":350},[115,85415,2060],{"class":262},[115,85417,85418,85420],{"class":117,"line":365},[115,85419,4883],{"class":121},[115,85421,4886],{"class":125},[115,85423,85424,85426],{"class":117,"line":380},[115,85425,2065],{"class":121},[115,85427,2068],{"class":125},[115,85429,85430,85432],{"class":117,"line":487},[115,85431,2073],{"class":121},[115,85433,2068],{"class":125},[115,85435,85436,85438],{"class":117,"line":2095},[115,85437,2081],{"class":121},[115,85439,4905],{"class":125},[115,85441,85442,85444],{"class":117,"line":2104},[115,85443,2089],{"class":121},[115,85445,4912],{"class":125},[115,85447,85448,85450,85453,85455],{"class":117,"line":2113},[115,85449,2107],{"class":121},[115,85451,85452],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A yourproject worker --",[115,85454,4922],{"class":121},[115,85456,4925],{"class":125},[115,85458,85459,85461],{"class":117,"line":2122},[115,85460,2116],{"class":121},[115,85462,2119],{"class":125},[115,85464,85465,85467],{"class":117,"line":2131},[115,85466,2125],{"class":121},[115,85468,2128],{"class":125},[115,85470,85471,85473],{"class":117,"line":2136},[115,85472,9286],{"class":121},[115,85474,85475],{"class":125},"=300\n",[115,85477,85478,85480],{"class":117,"line":2142},[115,85479,71923],{"class":121},[115,85481,85482],{"class":125},"=control-group\n",[115,85484,85485,85487],{"class":117,"line":2273},[115,85486,57864],{"class":121},[115,85488,57859],{"class":125},[115,85490,85491],{"class":117,"line":2282},[115,85492,310],{"emptyLinePlaceholder":309},[115,85494,85495],{"class":117,"line":2291},[115,85496,2139],{"class":262},[115,85498,85499,85501],{"class":117,"line":2299},[115,85500,2145],{"class":121},[115,85502,2148],{"class":125},[16,85504,85505],{},"This assumes:",[63,85507,85508,85513,85518],{},[66,85509,85510,85511],{},"Celery is installed in ",[20,85512,23122],{},[66,85514,85515,85516],{},"your Django code is under ",[20,85517,14814],{},[66,85519,85520,85521],{},"the app import path is ",[20,85522,85199],{},[16,85524,85525],{},"If the broker runs locally on the same host, you can extend ordering, for example:",[106,85527,85529],{"className":2026,"code":85528,"language":2028,"meta":111,"style":111},"After=network-online.target redis.service\n",[20,85530,85531],{"__ignoreMap":111},[115,85532,85533,85535],{"class":117,"line":118},[115,85534,2048],{"class":121},[115,85536,85537],{"class":125},"=network-online.target redis.service\n",[52,85539,85541],{"id":85540},"set-environment-variables-safely","Set environment variables safely",[16,85543,85544],{},"Create the env file:",[106,85546,85548],{"className":108,"code":85547,"language":110,"meta":111,"style":111},"sudo install -d -m 0750 -o root -g django \u002Fetc\u002Fmyapp\nsudo touch \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chown root:django \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chmod 0640 \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[20,85549,85550,85574,85582,85593],{"__ignoreMap":111},[115,85551,85552,85554,85556,85558,85560,85562,85564,85567,85570,85572],{"class":117,"line":118},[115,85553,2001],{"class":262},[115,85555,6600],{"class":132},[115,85557,1019],{"class":202},[115,85559,12284],{"class":202},[115,85561,74090],{"class":202},[115,85563,4012],{"class":202},[115,85565,85566],{"class":132}," root",[115,85568,85569],{"class":202}," -g",[115,85571,85299],{"class":132},[115,85573,23343],{"class":132},[115,85575,85576,85578,85580],{"class":117,"line":136},[115,85577,2001],{"class":262},[115,85579,63786],{"class":132},[115,85581,23352],{"class":132},[115,85583,85584,85586,85588,85591],{"class":117,"line":149},[115,85585,2001],{"class":262},[115,85587,6733],{"class":132},[115,85589,85590],{"class":132}," root:django",[115,85592,23352],{"class":132},[115,85594,85595,85597,85599,85601],{"class":117,"line":162},[115,85596,2001],{"class":262},[115,85598,12480],{"class":132},[115,85600,74894],{"class":202},[115,85602,23352],{"class":132},[16,85604,9761,85605,241],{},[20,85606,82528],{},[106,85608,85610],{"className":108,"code":85609,"language":110,"meta":111,"style":111},"DJANGO_SETTINGS_MODULE=yourproject.settings.production\nDATABASE_URL=postgres:\u002F\u002Fmyapp:password@127.0.0.1:5432\u002Fmyapp\nCELERY_BROKER_URL=redis:\u002F\u002F127.0.0.1:6379\u002F0\nCELERY_RESULT_BACKEND=redis:\u002F\u002F127.0.0.1:6379\u002F1\n",[20,85611,85612,85621,85630,85639],{"__ignoreMap":111},[115,85613,85614,85616,85618],{"class":117,"line":118},[115,85615,5074],{"class":125},[115,85617,129],{"class":121},[115,85619,85620],{"class":132},"yourproject.settings.production\n",[115,85622,85623,85625,85627],{"class":117,"line":136},[115,85624,10873],{"class":125},[115,85626,129],{"class":121},[115,85628,85629],{"class":132},"postgres:\u002F\u002Fmyapp:password@127.0.0.1:5432\u002Fmyapp\n",[115,85631,85632,85634,85636],{"class":117,"line":149},[115,85633,5201],{"class":125},[115,85635,129],{"class":121},[115,85637,85638],{"class":132},"redis:\u002F\u002F127.0.0.1:6379\u002F0\n",[115,85640,85641,85643,85645],{"class":117,"line":162},[115,85642,5206],{"class":125},[115,85644,129],{"class":121},[115,85646,85647],{"class":132},"redis:\u002F\u002F127.0.0.1:6379\u002F1\n",[16,85649,85650],{},"Only include secrets the worker actually needs. Avoid exposing unrelated application secrets to the Celery service environment.",[16,85652,85653],{},"Do not hardcode secrets inside the unit file if that file is managed in git or copied broadly between hosts.",[52,85655,85657],{"id":85656},"configure-restart-behavior-and-limits","Configure restart behavior and limits",[16,85659,85660],{},"Good defaults for most Django Celery workers:",[63,85662,85663,85668,85674,85680],{},[66,85664,85665,85667],{},[20,85666,57842],{}," to restart crashed workers",[66,85669,85670,85673],{},[20,85671,85672],{},"RestartSec=5"," to avoid tight restart loops",[66,85675,85676,85679],{},[20,85677,85678],{},"TimeoutStopSec=300"," to allow more time for graceful shutdown",[66,85681,85682,85685],{},[20,85683,85684],{},"KillMode=control-group"," so related processes are stopped together",[16,85687,85688,85689,85691],{},"If your tasks can run for a long time, increase ",[20,85690,9286],{}," further so workers can shut down cleanly during deploys.",[52,85693,85695],{"id":85694},"control-startup-ordering","Control startup ordering",[16,85697,85698,85700,85701,85703],{},[20,85699,85136],{}," is a better default than ",[20,85702,85140],{}," for workers that depend on a remote broker. It does not guarantee Redis or RabbitMQ is healthy, but it reduces boot-time startup races.",[16,85705,85706],{},"If the broker runs locally on the same host and has a named service, add ordering such as:",[106,85708,85709],{"className":2026,"code":85528,"language":2028,"meta":111,"style":111},[20,85710,85711],{"__ignoreMap":111},[115,85712,85713,85715],{"class":117,"line":118},[115,85714,2048],{"class":121},[115,85716,85537],{"class":125},[11,85718,85720],{"id":85719},"step-3-reload-systemd-and-start-the-worker","Step 3 - Reload systemd and start the worker",[52,85722,85724],{"id":85723},"reload-unit-files","Reload unit files",[106,85726,85727],{"className":108,"code":57893,"language":110,"meta":111,"style":111},[20,85728,85729],{"__ignoreMap":111},[115,85730,85731,85733,85735],{"class":117,"line":118},[115,85732,2001],{"class":262},[115,85734,3480],{"class":132},[115,85736,4984],{"class":132},[52,85738,85740],{"id":85739},"enable-service-at-boot","Enable service at boot",[106,85742,85744],{"className":108,"code":85743,"language":110,"meta":111,"style":111},"sudo systemctl enable celery\n",[20,85745,85746],{"__ignoreMap":111},[115,85747,85748,85750,85752,85754],{"class":117,"line":118},[115,85749,2001],{"class":262},[115,85751,3480],{"class":132},[115,85753,8567],{"class":132},[115,85755,4703],{"class":132},[52,85757,85759],{"id":85758},"start-and-inspect-service-state","Start and inspect service state",[106,85761,85763],{"className":108,"code":85762,"language":110,"meta":111,"style":111},"sudo systemctl start celery\nsudo systemctl status celery\n",[20,85764,85765,85775],{"__ignoreMap":111},[115,85766,85767,85769,85771,85773],{"class":117,"line":118},[115,85768,2001],{"class":262},[115,85770,3480],{"class":132},[115,85772,15489],{"class":132},[115,85774,4703],{"class":132},[115,85776,85777,85779,85781,85783],{"class":117,"line":136},[115,85778,2001],{"class":262},[115,85780,3480],{"class":132},[115,85782,1984],{"class":132},[115,85784,4703],{"class":132},[16,85786,85787],{},"Expected status should include something like:",[106,85789,85792],{"className":85790,"code":85791,"language":247,"meta":111},[245],"Active: active (running)\n",[20,85793,85791],{"__ignoreMap":111},[16,85795,85796],{},"You can inspect the final installed definition with:",[106,85798,85800],{"className":108,"code":85799,"language":110,"meta":111,"style":111},"systemctl cat celery\n",[20,85801,85802],{"__ignoreMap":111},[115,85803,85804,85806,85808],{"class":117,"line":118},[115,85805,1981],{"class":262},[115,85807,4973],{"class":132},[115,85809,4703],{"class":132},[52,85811,85813],{"id":85812},"read-logs-with-journalctl","Read logs with journalctl",[106,85815,85817],{"className":108,"code":85816,"language":110,"meta":111,"style":111},"sudo journalctl -u celery -f\n",[20,85818,85819],{"__ignoreMap":111},[115,85820,85821,85823,85825,85827,85829],{"class":117,"line":118},[115,85822,2001],{"class":262},[115,85824,5030],{"class":132},[115,85826,2788],{"class":202},[115,85828,5005],{"class":132},[115,85830,36482],{"class":202},[16,85832,85833],{},"Look for lines showing:",[63,85835,85836,85839,85842,85845],{},[66,85837,85838],{},"Celery version",[66,85840,85841],{},"broker connection established",[66,85843,85844],{},"registered tasks loaded",[66,85846,85847],{},"worker ready",[16,85849,85850,85851,85853],{},"If startup fails, ",[20,85852,2785],{}," is usually the fastest way to see import errors, missing env vars, or permission problems.",[11,85855,85857],{"id":85856},"step-4-verify-task-execution-end-to-end","Step 4 - Verify task execution end to end",[16,85859,85860],{},"Starting the service is not enough. Confirm it actually consumes tasks.",[52,85862,85864],{"id":85863},"queue-a-test-task-from-django-shell-or-app-code","Queue a test task from Django shell or app code",[16,85866,85867],{},"If you have a simple test task, open Django shell:",[106,85869,85871],{"className":108,"code":85870,"language":110,"meta":111,"style":111},"cd \u002Fsrv\u002Fmyapp\u002Fcurrent\n\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fpython manage.py shell\n",[20,85872,85873,85879],{"__ignoreMap":111},[115,85874,85875,85877],{"class":117,"line":118},[115,85876,5303],{"class":202},[115,85878,5306],{"class":132},[115,85880,85881,85883,85885],{"class":117,"line":136},[115,85882,28343],{"class":262},[115,85884,1117],{"class":132},[115,85886,6070],{"class":132},[16,85888,85889],{},"Then queue a task:",[106,85891,85893],{"className":2369,"code":85892,"language":1114,"meta":111,"style":111},"from yourapp.tasks import example_task\nexample_task.delay()\n",[20,85894,85895,85907],{"__ignoreMap":111},[115,85896,85897,85899,85902,85904],{"class":117,"line":118},[115,85898,5621],{"class":121},[115,85900,85901],{"class":125}," yourapp.tasks ",[115,85903,5613],{"class":121},[115,85905,85906],{"class":125}," example_task\n",[115,85908,85909],{"class":117,"line":136},[115,85910,85911],{"class":125},"example_task.delay()\n",[52,85913,85915],{"id":85914},"confirm-the-worker-consumes-the-task","Confirm the worker consumes the task",[16,85917,85918],{},"In the worker logs:",[106,85920,85921],{"className":108,"code":85816,"language":110,"meta":111,"style":111},[20,85922,85923],{"__ignoreMap":111},[115,85924,85925,85927,85929,85931,85933],{"class":117,"line":118},[115,85926,2001],{"class":262},[115,85928,5030],{"class":132},[115,85930,2788],{"class":202},[115,85932,5005],{"class":132},[115,85934,36482],{"class":202},[16,85936,85937],{},"You should see the task being received and finished.",[52,85939,85941],{"id":85940},"check-logs-for-import-errors-permission-errors-and-broker-failures","Check logs for import errors, permission errors, and broker failures",[16,85943,85944],{},"Typical bad patterns:",[63,85946,85947,85951,85955,85960],{},[66,85948,85949],{},[20,85950,5068],{},[66,85952,85953],{},[20,85954,59731],{},[66,85956,85957],{},[20,85958,85959],{},"Permission denied",[66,85961,85962],{},"broker connection refused or authentication errors",[16,85964,85965,85966,85968],{},"If it works in your shell but fails under ",[20,85967,1277],{},", the problem is usually one of:",[63,85970,85971,85975,85979,85981],{},[66,85972,20107,85973],{},[20,85974,2089],{},[66,85976,31412,85977],{},[20,85978,2081],{},[66,85980,31409],{},[66,85982,85983],{},"wrong service user permissions",[11,85985,85987],{"id":85986},"step-5-add-optional-celery-beat-under-systemd","Step 5 - Add optional Celery Beat under systemd",[52,85989,85991],{"id":85990},"when-beat-should-be-a-separate-service","When Beat should be a separate service",[16,85993,85994,85995,85997],{},"Run Celery Beat as a separate service if you use scheduled tasks. Do not combine worker and beat into one ",[20,85996,1277],{}," process.",[52,85999,86001],{"id":86000},"minimal-service-structure-for-beat","Minimal service structure for Beat",[16,86003,8628,86004,241],{},[20,86005,86006],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcelerybeat.service",[106,86008,86010],{"className":2026,"code":86009,"language":2028,"meta":111,"style":111},"[Unit]\nDescription=Celery Beat for Django app\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=simple\nUser=django\nGroup=django\nWorkingDirectory=\u002Fsrv\u002Fmyapp\u002Fcurrent\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A yourproject beat --loglevel=INFO\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=300\nNoNewPrivileges=true\n\n[Install]\nWantedBy=multi-user.target\n",[20,86011,86012,86016,86023,86029,86035,86039,86043,86049,86055,86061,86067,86073,86084,86090,86096,86102,86108,86112,86116],{"__ignoreMap":111},[115,86013,86014],{"class":117,"line":118},[115,86015,2035],{"class":262},[115,86017,86018,86020],{"class":117,"line":136},[115,86019,2040],{"class":121},[115,86021,86022],{"class":125},"=Celery Beat for Django app\n",[115,86024,86025,86027],{"class":117,"line":149},[115,86026,85398],{"class":121},[115,86028,85401],{"class":125},[115,86030,86031,86033],{"class":117,"line":162},[115,86032,2048],{"class":121},[115,86034,85401],{"class":125},[115,86036,86037],{"class":117,"line":175},[115,86038,310],{"emptyLinePlaceholder":309},[115,86040,86041],{"class":117,"line":350},[115,86042,2060],{"class":262},[115,86044,86045,86047],{"class":117,"line":365},[115,86046,4883],{"class":121},[115,86048,4886],{"class":125},[115,86050,86051,86053],{"class":117,"line":380},[115,86052,2065],{"class":121},[115,86054,2068],{"class":125},[115,86056,86057,86059],{"class":117,"line":487},[115,86058,2073],{"class":121},[115,86060,2068],{"class":125},[115,86062,86063,86065],{"class":117,"line":2095},[115,86064,2081],{"class":121},[115,86066,4905],{"class":125},[115,86068,86069,86071],{"class":117,"line":2104},[115,86070,2089],{"class":121},[115,86072,4912],{"class":125},[115,86074,86075,86077,86080,86082],{"class":117,"line":2113},[115,86076,2107],{"class":121},[115,86078,86079],{"class":125},"=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fcelery -A yourproject beat --",[115,86081,4922],{"class":121},[115,86083,4925],{"class":125},[115,86085,86086,86088],{"class":117,"line":2122},[115,86087,2116],{"class":121},[115,86089,2119],{"class":125},[115,86091,86092,86094],{"class":117,"line":2131},[115,86093,2125],{"class":121},[115,86095,2128],{"class":125},[115,86097,86098,86100],{"class":117,"line":2136},[115,86099,9286],{"class":121},[115,86101,85475],{"class":125},[115,86103,86104,86106],{"class":117,"line":2142},[115,86105,57864],{"class":121},[115,86107,57859],{"class":125},[115,86109,86110],{"class":117,"line":2273},[115,86111,310],{"emptyLinePlaceholder":309},[115,86113,86114],{"class":117,"line":2282},[115,86115,2139],{"class":262},[115,86117,86118,86120],{"class":117,"line":2291},[115,86119,2145],{"class":121},[115,86121,2148],{"class":125},[16,86123,5144],{},[106,86125,86127],{"className":108,"code":86126,"language":110,"meta":111,"style":111},"sudo systemctl daemon-reload\nsudo systemctl enable celerybeat\nsudo systemctl start celerybeat\n",[20,86128,86129,86137,86148],{"__ignoreMap":111},[115,86130,86131,86133,86135],{"class":117,"line":118},[115,86132,2001],{"class":262},[115,86134,3480],{"class":132},[115,86136,4984],{"class":132},[115,86138,86139,86141,86143,86145],{"class":117,"line":136},[115,86140,2001],{"class":262},[115,86142,3480],{"class":132},[115,86144,8567],{"class":132},[115,86146,86147],{"class":132}," celerybeat\n",[115,86149,86150,86152,86154,86156],{"class":117,"line":149},[115,86151,2001],{"class":262},[115,86153,3480],{"class":132},[115,86155,15489],{"class":132},[115,86157,86147],{"class":132},[52,86159,86161],{"id":86160},"avoid-duplicate-schedulers-in-multi-host-setups","Avoid duplicate schedulers in multi-host setups",[16,86163,86164],{},"Only run one Beat scheduler for a given schedule unless you are using a setup specifically designed for distributed scheduling. Multiple Beat instances can enqueue duplicate jobs.",[11,86166,86168],{"id":86167},"step-6-make-restarts-and-deployments-safe","Step 6 - Make restarts and deployments safe",[52,86170,86172],{"id":86171},"restart-the-worker-after-code-deploys","Restart the worker after code deploys",[16,86174,86175],{},"Celery workers keep old Python code in memory. After a deploy, restart them:",[106,86177,86179],{"className":108,"code":86178,"language":110,"meta":111,"style":111},"sudo systemctl restart celery\n",[20,86180,86181],{"__ignoreMap":111},[115,86182,86183,86185,86187,86189],{"class":117,"line":118},[115,86184,2001],{"class":262},[115,86186,3480],{"class":132},[115,86188,3483],{"class":132},[115,86190,4703],{"class":132},[16,86192,86193],{},"If you run Beat:",[106,86195,86197],{"className":108,"code":86196,"language":110,"meta":111,"style":111},"sudo systemctl restart celerybeat\n",[20,86198,86199],{"__ignoreMap":111},[115,86200,86201,86203,86205,86207],{"class":117,"line":118},[115,86202,2001],{"class":262},[115,86204,3480],{"class":132},[115,86206,3483],{"class":132},[115,86208,86147],{"class":132},[16,86210,38870,86211,86214,86215,86217,86218,86220],{},[20,86212,86213],{},"systemctl restart"," if tasks are long-running, because ",[20,86216,1277],{}," will eventually force-stop the worker when ",[20,86219,9286],{}," expires.",[52,86222,86224],{"id":86223},"handle-in-flight-tasks-carefully","Handle in-flight tasks carefully",[16,86226,86227],{},"A normal stop is preferable to a forced kill:",[106,86229,86231],{"className":108,"code":86230,"language":110,"meta":111,"style":111},"sudo systemctl stop celery\n",[20,86232,86233],{"__ignoreMap":111},[115,86234,86235,86237,86239,86241],{"class":117,"line":118},[115,86236,2001],{"class":262},[115,86238,3480],{"class":132},[115,86240,9987],{"class":132},[115,86242,4703],{"class":132},[16,86244,86245,86247,86248,86250],{},[20,86246,9286],{}," controls how long ",[20,86249,1277],{}," waits before killing the service. Set it high enough for your shutdown pattern, then test real stop and restart behavior in logs. Celery workers commonly use prefork child processes, so validate that your worker exits cleanly and that child processes are not being killed too early during deploys.",[52,86252,86254],{"id":86253},"coordinate-migrations-and-async-tasks","Coordinate migrations and async tasks",[16,86256,86257],{},"If a deploy includes database schema changes, tasks may fail if workers run new code against old schema or old code against new schema. Plan migrations and worker restarts together, especially for tasks that read or write changed tables.",[16,86259,86260],{},"A safer pattern is:",[1173,86262,86263,86266,86268,86270,86273],{},[66,86264,86265],{},"deploy code that stays compatible with both old and new schema where possible",[66,86267,1186],{},[66,86269,38654],{},[66,86271,86272],{},"verify task execution",[66,86274,86275],{},"remove compatibility code in a later deploy if needed",[16,86277,86278,86279,86281],{},"Rollback note: if a deploy breaks task execution, switch ",[20,86280,13654],{}," back to the previous release, restart the worker, and confirm tasks are being consumed again. Also check whether queued tasks contain payloads or assumptions tied to the failed release.",[11,86283,1321],{"id":1320},[16,86285,86286,86287,86289],{},"This setup works because ",[20,86288,1277],{}," gives Celery a consistent runtime environment:",[63,86291,86292,86295,86298,86301,86304,86307],{},[66,86293,86294],{},"fixed user and group",[66,86296,86297],{},"fixed working directory",[66,86299,86300],{},"explicit environment variables",[66,86302,86303],{},"controlled restart behavior",[66,86305,86306],{},"boot-time startup",[66,86308,86309],{},"centralized logs through the journal",[16,86311,86312],{},"That removes the common “works in shell, fails in production” problem.",[16,86314,86315,86316,86318,86319,86321],{},"For most Django deployments on Ubuntu or other Linux distributions, ",[20,86317,1277],{}," is the simplest production process manager. Container-based deployments may use a different supervisor model, but on a standard VM or dedicated host, ",[20,86320,1277],{}," is usually the right default.",[52,86323,41766],{"id":41765},[16,86325,86326,86327,86330],{},"If you repeat this setup across staging, production, and multiple projects, turn it into a reusable template. The first things worth scripting are unit file creation, env file permissions, ",[20,86328,86329],{},"daemon-reload",", service enable\u002Fstart, and post-deploy worker restarts. That reduces path mistakes and inconsistent restart policies.",[11,86332,86334],{"id":86333},"edge-cases-and-production-notes","Edge cases and production notes",[63,86336,86337,86340,86343,86346,86354,86360,86363,86366,86369],{},[66,86338,86339],{},"Keep env files readable only by root and the app group.",[66,86341,86342],{},"Do not commit secrets in unit files or repository-managed config.",[66,86344,86345],{},"Verify the service user can read the project and execute the virtualenv binaries.",[66,86347,86348,86349,86351,86352,24680],{},"Confirm ",[20,86350,2081],{}," points to the correct release or ",[20,86353,13654],{},[66,86355,86356,86357,86359],{},"If you use symlinked deploys, restart workers after changing ",[20,86358,13654],{},", or they may keep using old code already loaded into memory.",[66,86361,86362],{},"If you run multiple worker services, give them distinct service names and queue bindings so you can monitor and restart them independently.",[66,86364,86365],{},"If you run more than one worker on the same host, set explicit worker names or separate service definitions so logs and Celery inspection output are easier to interpret.",[66,86367,86368],{},"Under load, open file limits can become a real issue. If you see connection or descriptor errors, review systemd resource limits for the service instead of only tuning Celery settings.",[66,86370,86371],{},"If tasks are long-running or critical, test stop, restart, and rollback behavior with real jobs before relying on the service in production.",[11,86373,6115],{"id":6114},[16,86375,86376],{},"If a service change breaks production:",[1173,86378,86379,86382,86387,86393,86410,86412],{},[66,86380,86381],{},"restore the previous unit file if it changed",[66,86383,7902,86384],{},[20,86385,86386],{},"sudo systemctl daemon-reload",[66,86388,86389,86390,86392],{},"point ",[20,86391,13654],{}," back to the previous release if needed",[66,86394,86395,86396],{},"restart the worker:\n",[106,86397,86398],{"className":108,"code":86178,"language":110,"meta":111,"style":111},[20,86399,86400],{"__ignoreMap":111},[115,86401,86402,86404,86406,86408],{"class":117,"line":118},[115,86403,2001],{"class":262},[115,86405,3480],{"class":132},[115,86407,3483],{"class":132},[115,86409,4703],{"class":132},[66,86411,58249],{},[66,86413,86414],{},"check whether failed tasks are retrying, stuck in queue, or were published with payloads that only the failed release understands",[16,86416,86417],{},"After rollback, queue one known-safe test task and confirm it is consumed.",[11,86419,41313],{"id":41312},[63,86421,86422,86428,86432,86436,86441,86444,86450,86455],{},[66,86423,86424,86425],{},"wrong app module path in ",[20,86426,86427],{},"-A yourproject",[66,86429,20107,86430],{},[20,86431,5074],{},[66,86433,31412,86434],{},[20,86435,2081],{},[66,86437,86438,86439],{},"wrong virtualenv path in ",[20,86440,2107],{},[66,86442,86443],{},"broker reachable from shell but not from the service environment",[66,86445,86446,86447,86449],{},"using only ",[20,86448,85140],{}," and hitting boot-time broker races",[66,86451,86452,86454],{},[20,86453,9286],{}," too low for task runtime",[66,86456,86457],{},"Beat running on multiple hosts and creating duplicate scheduled jobs",[11,86459,1386],{"id":1385},[16,86461,86462,86463,211],{},"For the background model behind workers, queues, and brokers, see ",[1395,86464,86465],{"href":6180},"How Celery Works in Django Production",[16,86467,86468,86469,211],{},"If your broker is not stable yet, start with ",[1395,86470,86471],{"href":6180},"Configure Redis for Django and Celery in Production",[16,86473,86474,86475,211],{},"For the main web application process layout, see ",[1395,86476,2986],{"href":2985},[16,86478,86479,86480,211],{},"If the service refuses to start, use ",[1395,86481,86482],{"href":10169},"Why Celery Workers Fail to Start in Production",[11,86484,1420],{"id":1419},[52,86486,86488],{"id":86487},"how-do-i-run-celery-workers-automatically-after-a-server-reboot","How do I run Celery workers automatically after a server reboot?",[16,86490,86491],{},"Enable the service:",[106,86493,86494],{"className":108,"code":85743,"language":110,"meta":111,"style":111},[20,86495,86496],{"__ignoreMap":111},[115,86497,86498,86500,86502,86504],{"class":117,"line":118},[115,86499,2001],{"class":262},[115,86501,3480],{"class":132},[115,86503,8567],{"class":132},[115,86505,4703],{"class":132},[16,86507,86508],{},"That creates the boot-time dependency so the worker starts automatically.",[52,86510,86512,86513,86515],{"id":86511},"should-celery-beat-run-in-the-same-systemd-service-as-the-worker","Should Celery Beat run in the same ",[20,86514,1277],{}," service as the worker?",[16,86517,86518],{},"No. Run Beat as a separate service. It has a different role and should be restarted, monitored, and scheduled independently.",[52,86520,86522,86523,4474],{"id":86521},"why-does-celery-work-in-my-shell-but-fail-under-systemd","Why does Celery work in my shell but fail under ",[20,86524,1277],{},[16,86526,86527,86528,86530,86531,1153,86533,86535,86536,86538],{},"Usually because your shell provides environment variables, a different current directory, or an activated virtualenv that ",[20,86529,1277],{}," does not have. Set ",[20,86532,2081],{},[20,86534,2089],{},", and the full ",[20,86537,2107],{}," path explicitly.",[52,86540,86542],{"id":86541},"how-do-i-restart-celery-workers-safely-during-a-deploy","How do I restart Celery workers safely during a deploy?",[16,86544,86545],{},"Deploy the new code, then restart the worker with:",[106,86547,86548],{"className":108,"code":86178,"language":110,"meta":111,"style":111},[20,86549,86550],{"__ignoreMap":111},[115,86551,86552,86554,86556,86558],{"class":117,"line":118},[115,86553,2001],{"class":262},[115,86555,3480],{"class":132},[115,86557,3483],{"class":132},[115,86559,4703],{"class":132},[16,86561,86562,86563,86565],{},"If tasks are long-running, review ",[20,86564,9286],{}," and test shutdown behavior so in-flight work is not killed too quickly.",[52,86567,86569,86570,86572],{"id":86568},"what-is-the-best-way-to-store-django-and-broker-environment-variables-for-a-systemd-service","What is the best way to store Django and broker environment variables for a ",[20,86571,1277],{}," service?",[16,86574,86575,86576,86578],{},"Use a dedicated env file outside the repository, referenced with ",[20,86577,80138],{},". Lock down permissions so only root and the app group can read it, and only include variables the worker actually needs.",[1485,86580,86581],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":111,"searchDepth":149,"depth":149,"links":86583},[86584,86585,86586,86587,86592,86599,86605,86610,86615,86620,86623,86624,86625,86626,86627],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":85149,"depth":136,"text":85150,"children":86588},[86589,86590,86591],{"id":85156,"depth":149,"text":85157},{"id":85203,"depth":149,"text":85204},{"id":85244,"depth":149,"text":85245},{"id":85360,"depth":136,"text":85361,"children":86593},[86594,86595,86596,86597,86598],{"id":85364,"depth":149,"text":85365},{"id":85371,"depth":149,"text":85372},{"id":85540,"depth":149,"text":85541},{"id":85656,"depth":149,"text":85657},{"id":85694,"depth":149,"text":85695},{"id":85719,"depth":136,"text":85720,"children":86600},[86601,86602,86603,86604],{"id":85723,"depth":149,"text":85724},{"id":85739,"depth":149,"text":85740},{"id":85758,"depth":149,"text":85759},{"id":85812,"depth":149,"text":85813},{"id":85856,"depth":136,"text":85857,"children":86606},[86607,86608,86609],{"id":85863,"depth":149,"text":85864},{"id":85914,"depth":149,"text":85915},{"id":85940,"depth":149,"text":85941},{"id":85986,"depth":136,"text":85987,"children":86611},[86612,86613,86614],{"id":85990,"depth":149,"text":85991},{"id":86000,"depth":149,"text":86001},{"id":86160,"depth":149,"text":86161},{"id":86167,"depth":136,"text":86168,"children":86616},[86617,86618,86619],{"id":86171,"depth":149,"text":86172},{"id":86223,"depth":149,"text":86224},{"id":86253,"depth":149,"text":86254},{"id":1320,"depth":136,"text":1321,"children":86621},[86622],{"id":41765,"depth":149,"text":41766},{"id":86333,"depth":136,"text":86334},{"id":6114,"depth":136,"text":6115},{"id":41312,"depth":136,"text":41313},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":86628},[86629,86630,86632,86634,86635],{"id":86487,"depth":149,"text":86488},{"id":86511,"depth":149,"text":86631},"Should Celery Beat run in the same systemd service as the worker?",{"id":86521,"depth":149,"text":86633},"Why does Celery work in my shell but fail under systemd?",{"id":86541,"depth":149,"text":86542},{"id":86568,"depth":149,"text":86636},"What is the best way to store Django and broker environment variables for a systemd service?","Running Celery workers manually in production is fragile. If you start a worker from a shell and disconnect, the process may stop.",{},"\u002Frun-celery-with-systemd-django","23",[32218,6180,2985],{"title":85057,"description":86637},[1557,5319,1277],"run-celery-with-systemd-django",[1557,5319,1277],"726-k6eaNqaCpQWvI_8AJTG1G3LjCk12w85Zd2tA_bg",{"id":86648,"title":86649,"body":86650,"category":1541,"description":87899,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":1546,"meta":87900,"navigation":309,"path":87901,"priority":50898,"related":87902,"role":1553,"section":1554,"seo":87903,"stack":87904,"stem":87906,"tags":87907,"type":1561,"__hash__":87908},"articles\u002Fsetup-sentry-for-django-production.md","Set Up Sentry Error Monitoring for Django",{"type":8,"value":86651,"toc":87870},[86652,86654,86657,86660,86663,86665,86668,86710,86712,86716,86719,86722,86733,86736,86750,86753,86771,86774,86806,86811,86815,86818,86832,86835,86849,86856,86864,86869,86873,86876,87046,87049,87056,87230,87242,87247,87255,87259,87263,87266,87292,87295,87309,87312,87337,87345,87348,87352,87404,87410,87414,87417,87434,87437,87442,87446,87449,87452,87493,87496,87548,87551,87554,87557,87574,87577,87580,87588,87592,87595,87606,87609,87612,87623,87627,87630,87633,87653,87660,87665,87669,87675,87691,87694,87698,87701,87703,87706,87709,87732,87738,87740,87799,87801,87804,87824,87826,87830,87833,87837,87840,87844,87850,87854,87857,87861,87867],[11,86653,14],{"id":13},[16,86655,86656],{},"Local logs are not enough once a Django app is in production. Exceptions can happen after deploys, during background jobs, or only under real user traffic patterns. If you rely only on Gunicorn logs, reverse proxy logs, or manual SSH checks, production failures are easy to miss or slow to triage.",[16,86658,86659],{},"A practical Sentry setup for Django gives you centralized exception tracking, issue grouping, release visibility, and alerting. It does not replace server logs, but it helps you capture production errors quickly and tie them to the correct environment and release.",[16,86661,86662],{},"This guide shows how to configure Sentry for Django in a production-safe way: install the SDK, initialize it from environment variables, verify it after deploy, reduce noise, and keep rollback simple.",[11,86664,30],{"id":29},[16,86666,86667],{},"To set up Sentry error monitoring for Django safely in production:",[1173,86669,86670,86673,86679,86685,86698,86701,86704],{},[66,86671,86672],{},"Create a Sentry project and copy the DSN.",[66,86674,86675,86676,211],{},"Install and pin ",[20,86677,86678],{},"sentry-sdk",[66,86680,86681,86682,211],{},"Initialize Sentry in Django settings with ",[20,86683,86684],{},"DjangoIntegration",[66,86686,86687,86688,1153,86691,20346,86694,86697],{},"Pass ",[20,86689,86690],{},"SENTRY_DSN",[20,86692,86693],{},"SENTRY_ENVIRONMENT",[20,86695,86696],{},"SENTRY_RELEASE"," through environment variables.",[66,86699,86700],{},"Deploy and trigger a controlled test exception.",[66,86702,86703],{},"Verify the event appears in the correct environment and release.",[66,86705,86706,86707,86709],{},"If the rollout causes problems, clear ",[20,86708,86690],{}," and restart or redeploy.",[11,86711,43],{"id":42},[11,86713,86715],{"id":86714},"_1-create-and-prepare-the-sentry-project","1) Create and prepare the Sentry project",[16,86717,86718],{},"Create a project in Sentry for your Django application. A standard Python or Django project is fine.",[16,86720,86721],{},"Before changing production settings, decide:",[63,86723,86724,86727,86730],{},[66,86725,86726],{},"which Sentry project will receive production events",[66,86728,86729],{},"whether staging will use the same project with a separate environment, or a separate project",[66,86731,86732],{},"what release identifier you will send, such as a git SHA or deploy version",[16,86734,86735],{},"At minimum, collect these values:",[63,86737,86738,86742,86746],{},[66,86739,86740],{},[20,86741,86690],{},[66,86743,86744],{},[20,86745,86693],{},[66,86747,86748],{},[20,86749,86696],{},[16,86751,86752],{},"A simple release strategy is to use the current git commit:",[106,86754,86756],{"className":108,"code":86755,"language":110,"meta":111,"style":111},"git rev-parse --short HEAD\n",[20,86757,86758],{"__ignoreMap":111},[115,86759,86760,86762,86765,86768],{"class":117,"line":118},[115,86761,13525],{"class":262},[115,86763,86764],{"class":132}," rev-parse",[115,86766,86767],{"class":202}," --short",[115,86769,86770],{"class":132}," HEAD\n",[16,86772,86773],{},"Example values:",[106,86775,86777],{"className":108,"code":86776,"language":110,"meta":111,"style":111},"SENTRY_DSN=\"https:\u002F\u002Fexample@o0.ingest.sentry.io\u002F0\"\nSENTRY_ENVIRONMENT=\"production\"\nSENTRY_RELEASE=\"myapp@a1b2c3d\"\n",[20,86778,86779,86788,86797],{"__ignoreMap":111},[115,86780,86781,86783,86785],{"class":117,"line":118},[115,86782,86690],{"class":125},[115,86784,129],{"class":121},[115,86786,86787],{"class":132},"\"https:\u002F\u002Fexample@o0.ingest.sentry.io\u002F0\"\n",[115,86789,86790,86792,86794],{"class":117,"line":136},[115,86791,86693],{"class":125},[115,86793,129],{"class":121},[115,86795,86796],{"class":132},"\"production\"\n",[115,86798,86799,86801,86803],{"class":117,"line":149},[115,86800,86696],{"class":125},[115,86802,129],{"class":121},[115,86804,86805],{"class":132},"\"myapp@a1b2c3d\"\n",[16,86807,86808,86810],{},[1226,86809,3515],{}," confirm you copied the DSN for the correct project before updating production config.",[11,86812,86814],{"id":86813},"_2-install-the-sentry-sdk","2) Install the Sentry SDK",[16,86816,86817],{},"Install the official SDK in your Django environment:",[106,86819,86821],{"className":108,"code":86820,"language":110,"meta":111,"style":111},"pip install sentry-sdk\n",[20,86822,86823],{"__ignoreMap":111},[115,86824,86825,86827,86829],{"class":117,"line":118},[115,86826,8618],{"class":262},[115,86828,6600],{"class":132},[115,86830,86831],{"class":132}," sentry-sdk\n",[16,86833,86834],{},"Pin the dependency so deploys stay reproducible:",[106,86836,86837],{"className":108,"code":58954,"language":110,"meta":111,"style":111},[20,86838,86839],{"__ignoreMap":111},[115,86840,86841,86843,86845,86847],{"class":117,"line":118},[115,86842,8618],{"class":262},[115,86844,16323],{"class":132},[115,86846,604],{"class":121},[115,86848,12353],{"class":132},[16,86850,86851,86852,86855],{},"If you manage dependencies with a lockfile or ",[20,86853,86854],{},"pyproject.toml",", update that file instead.",[16,86857,86858,86860,86861,86863],{},[1226,86859,3515],{}," confirm ",[20,86862,86678],{}," is included in the image, artifact, or virtual environment you actually deploy.",[16,86865,86866,86868],{},[1226,86867,47125],{}," if dependency resolution causes build failures, revert the dependency change and deploy without Sentry first.",[11,86870,86872],{"id":86871},"_3-configure-sentry-in-django-settings","3) Configure Sentry in Django settings",[16,86874,86875],{},"Add Sentry initialization to the Django settings module used in production. Do not hardcode the DSN in source control.",[106,86877,86879],{"className":2369,"code":86878,"language":1114,"meta":111,"style":111},"import os\n\nimport sentry_sdk\nfrom sentry_sdk.integrations.django import DjangoIntegration\n\nSENTRY_DSN = os.environ.get(\"SENTRY_DSN\")\nSENTRY_ENVIRONMENT = os.environ.get(\"SENTRY_ENVIRONMENT\", \"production\")\nSENTRY_RELEASE = os.environ.get(\"SENTRY_RELEASE\")\n\nif SENTRY_DSN:\n    sentry_sdk.init(\n        dsn=SENTRY_DSN,\n        integrations=[DjangoIntegration()],\n        environment=SENTRY_ENVIRONMENT,\n        release=SENTRY_RELEASE,\n        send_default_pii=False,\n        traces_sample_rate=0.0,\n    )\n",[20,86880,86881,86887,86891,86898,86910,86914,86927,86945,86958,86962,86971,86976,86987,86997,87008,87019,87030,87042],{"__ignoreMap":111},[115,86882,86883,86885],{"class":117,"line":118},[115,86884,5613],{"class":121},[115,86886,5616],{"class":125},[115,86888,86889],{"class":117,"line":136},[115,86890,310],{"emptyLinePlaceholder":309},[115,86892,86893,86895],{"class":117,"line":149},[115,86894,5613],{"class":121},[115,86896,86897],{"class":125}," sentry_sdk\n",[115,86899,86900,86902,86905,86907],{"class":117,"line":162},[115,86901,5621],{"class":121},[115,86903,86904],{"class":125}," sentry_sdk.integrations.django ",[115,86906,5613],{"class":121},[115,86908,86909],{"class":125}," DjangoIntegration\n",[115,86911,86912],{"class":117,"line":175},[115,86913,310],{"emptyLinePlaceholder":309},[115,86915,86916,86918,86920,86922,86925],{"class":117,"line":350},[115,86917,86690],{"class":202},[115,86919,2380],{"class":121},[115,86921,8884],{"class":125},[115,86923,86924],{"class":132},"\"SENTRY_DSN\"",[115,86926,2394],{"class":125},[115,86928,86929,86931,86933,86935,86938,86940,86943],{"class":117,"line":365},[115,86930,86693],{"class":202},[115,86932,2380],{"class":121},[115,86934,8884],{"class":125},[115,86936,86937],{"class":132},"\"SENTRY_ENVIRONMENT\"",[115,86939,1153],{"class":125},[115,86941,86942],{"class":132},"\"production\"",[115,86944,2394],{"class":125},[115,86946,86947,86949,86951,86953,86956],{"class":117,"line":380},[115,86948,86696],{"class":202},[115,86950,2380],{"class":121},[115,86952,8884],{"class":125},[115,86954,86955],{"class":132},"\"SENTRY_RELEASE\"",[115,86957,2394],{"class":125},[115,86959,86960],{"class":117,"line":487},[115,86961,310],{"emptyLinePlaceholder":309},[115,86963,86964,86966,86969],{"class":117,"line":2095},[115,86965,10833],{"class":121},[115,86967,86968],{"class":202}," SENTRY_DSN",[115,86970,2498],{"class":125},[115,86972,86973],{"class":117,"line":2104},[115,86974,86975],{"class":125},"    sentry_sdk.init(\n",[115,86977,86978,86981,86983,86985],{"class":117,"line":2113},[115,86979,86980],{"class":5680},"        dsn",[115,86982,129],{"class":121},[115,86984,86690],{"class":202},[115,86986,3354],{"class":125},[115,86988,86989,86992,86994],{"class":117,"line":2122},[115,86990,86991],{"class":5680},"        integrations",[115,86993,129],{"class":121},[115,86995,86996],{"class":125},"[DjangoIntegration()],\n",[115,86998,86999,87002,87004,87006],{"class":117,"line":2131},[115,87000,87001],{"class":5680},"        environment",[115,87003,129],{"class":121},[115,87005,86693],{"class":202},[115,87007,3354],{"class":125},[115,87009,87010,87013,87015,87017],{"class":117,"line":2136},[115,87011,87012],{"class":5680},"        release",[115,87014,129],{"class":121},[115,87016,86696],{"class":202},[115,87018,3354],{"class":125},[115,87020,87021,87024,87026,87028],{"class":117,"line":2142},[115,87022,87023],{"class":5680},"        send_default_pii",[115,87025,129],{"class":121},[115,87027,3364],{"class":202},[115,87029,3354],{"class":125},[115,87031,87032,87035,87037,87040],{"class":117,"line":2273},[115,87033,87034],{"class":5680},"        traces_sample_rate",[115,87036,129],{"class":121},[115,87038,87039],{"class":202},"0.0",[115,87041,3354],{"class":125},[115,87043,87044],{"class":117,"line":2282},[115,87045,16748],{"class":125},[16,87047,87048],{},"This enables exception tracking but leaves performance tracing disabled by default. That is a good baseline for production error monitoring because it keeps rollout simpler and avoids unexpected event volume.",[16,87050,87051,87052,87055],{},"If you need to filter noisy exceptions, add a ",[20,87053,87054],{},"before_send"," hook:",[106,87057,87059],{"className":2369,"code":87058,"language":1114,"meta":111,"style":111},"def before_send(event, hint):\n    exc_info = hint.get(\"exc_info\")\n    if exc_info is not None:\n        exc_type, exc_value, tb = exc_info\n        if exc_type.__name__ == \"DisallowedHost\":\n            return None\n    return event\n\nif SENTRY_DSN:\n    sentry_sdk.init(\n        dsn=SENTRY_DSN,\n        integrations=[DjangoIntegration()],\n        environment=SENTRY_ENVIRONMENT,\n        release=SENTRY_RELEASE,\n        send_default_pii=False,\n        traces_sample_rate=0.0,\n        before_send=before_send,\n    )\n",[20,87060,87061,87071,87086,87101,87111,87128,87135,87142,87146,87154,87158,87168,87176,87186,87196,87206,87216,87226],{"__ignoreMap":111},[115,87062,87063,87065,87068],{"class":117,"line":118},[115,87064,8808],{"class":121},[115,87066,87067],{"class":262}," before_send",[115,87069,87070],{"class":125},"(event, hint):\n",[115,87072,87073,87076,87078,87081,87084],{"class":117,"line":136},[115,87074,87075],{"class":125},"    exc_info ",[115,87077,129],{"class":121},[115,87079,87080],{"class":125}," hint.get(",[115,87082,87083],{"class":132},"\"exc_info\"",[115,87085,2394],{"class":125},[115,87087,87088,87090,87093,87095,87097,87099],{"class":117,"line":149},[115,87089,40975],{"class":121},[115,87091,87092],{"class":125}," exc_info ",[115,87094,65878],{"class":121},[115,87096,60574],{"class":121},[115,87098,65881],{"class":202},[115,87100,2498],{"class":125},[115,87102,87103,87106,87108],{"class":117,"line":162},[115,87104,87105],{"class":125},"        exc_type, exc_value, tb ",[115,87107,129],{"class":121},[115,87109,87110],{"class":125}," exc_info\n",[115,87112,87113,87115,87118,87120,87123,87126],{"class":117,"line":175},[115,87114,37216],{"class":121},[115,87116,87117],{"class":125}," exc_type.",[115,87119,8792],{"class":202},[115,87121,87122],{"class":121}," ==",[115,87124,87125],{"class":132}," \"DisallowedHost\"",[115,87127,2498],{"class":125},[115,87129,87130,87133],{"class":117,"line":350},[115,87131,87132],{"class":121},"            return",[115,87134,78872],{"class":202},[115,87136,87137,87139],{"class":117,"line":365},[115,87138,3822],{"class":121},[115,87140,87141],{"class":125}," event\n",[115,87143,87144],{"class":117,"line":380},[115,87145,310],{"emptyLinePlaceholder":309},[115,87147,87148,87150,87152],{"class":117,"line":487},[115,87149,10833],{"class":121},[115,87151,86968],{"class":202},[115,87153,2498],{"class":125},[115,87155,87156],{"class":117,"line":2095},[115,87157,86975],{"class":125},[115,87159,87160,87162,87164,87166],{"class":117,"line":2104},[115,87161,86980],{"class":5680},[115,87163,129],{"class":121},[115,87165,86690],{"class":202},[115,87167,3354],{"class":125},[115,87169,87170,87172,87174],{"class":117,"line":2113},[115,87171,86991],{"class":5680},[115,87173,129],{"class":121},[115,87175,86996],{"class":125},[115,87177,87178,87180,87182,87184],{"class":117,"line":2122},[115,87179,87001],{"class":5680},[115,87181,129],{"class":121},[115,87183,86693],{"class":202},[115,87185,3354],{"class":125},[115,87187,87188,87190,87192,87194],{"class":117,"line":2131},[115,87189,87012],{"class":5680},[115,87191,129],{"class":121},[115,87193,86696],{"class":202},[115,87195,3354],{"class":125},[115,87197,87198,87200,87202,87204],{"class":117,"line":2136},[115,87199,87023],{"class":5680},[115,87201,129],{"class":121},[115,87203,3364],{"class":202},[115,87205,3354],{"class":125},[115,87207,87208,87210,87212,87214],{"class":117,"line":2142},[115,87209,87034],{"class":5680},[115,87211,129],{"class":121},[115,87213,87039],{"class":202},[115,87215,3354],{"class":125},[115,87217,87218,87221,87223],{"class":117,"line":2273},[115,87219,87220],{"class":5680},"        before_send",[115,87222,129],{"class":121},[115,87224,87225],{"class":125},"before_send,\n",[115,87227,87228],{"class":117,"line":2282},[115,87229,16748],{"class":125},[16,87231,87232,87233,87235,87236,87238,87239,87241],{},"That example drops ",[20,87234,46086],{},", which often creates noise from random host header probes. Only filter ",[20,87237,46086],{}," after confirming ",[20,87240,2719],{}," is correctly configured; filtering should reduce hostile-traffic noise, not hide a real deployment misconfiguration.",[16,87243,87244,87246],{},[1226,87245,3515],{}," start Django locally or in staging with the same settings path and confirm startup succeeds.",[16,87248,87249,87251,87252,87254],{},[1226,87250,47125],{}," if app startup fails after this change, first clear ",[20,87253,86690],{}," or unset it in the runtime environment and restart or redeploy. Prefer config rollback over editing application code during an incident.",[11,87256,87258],{"id":87257},"_4-pass-environment-variables-during-deployment","4) Pass environment variables during deployment",[52,87260,87262],{"id":87261},"non-docker-example-with-systemd","Non-Docker example with systemd",[16,87264,87265],{},"Store runtime variables in an env file:",[106,87267,87269],{"className":2026,"code":87268,"language":2028,"meta":111,"style":111},"SENTRY_DSN=https:\u002F\u002Fexample@o0.ingest.sentry.io\u002F0\nSENTRY_ENVIRONMENT=production\nSENTRY_RELEASE=myapp@a1b2c3d\n",[20,87270,87271,87278,87285],{"__ignoreMap":111},[115,87272,87273,87275],{"class":117,"line":118},[115,87274,86690],{"class":121},[115,87276,87277],{"class":125},"=https:\u002F\u002Fexample@o0.ingest.sentry.io\u002F0\n",[115,87279,87280,87282],{"class":117,"line":136},[115,87281,86693],{"class":121},[115,87283,87284],{"class":125},"=production\n",[115,87286,87287,87289],{"class":117,"line":149},[115,87288,86696],{"class":121},[115,87290,87291],{"class":125},"=myapp@a1b2c3d\n",[16,87293,87294],{},"Reference it from your service:",[106,87296,87297],{"className":2026,"code":46951,"language":2028,"meta":111,"style":111},[20,87298,87299,87303],{"__ignoreMap":111},[115,87300,87301],{"class":117,"line":118},[115,87302,2060],{"class":262},[115,87304,87305,87307],{"class":117,"line":136},[115,87306,2089],{"class":121},[115,87308,4912],{"class":125},[16,87310,87311],{},"After updating the file:",[106,87313,87315],{"className":108,"code":87314,"language":110,"meta":111,"style":111},"sudo systemctl restart myapp\nsudo systemctl status myapp\n",[20,87316,87317,87327],{"__ignoreMap":111},[115,87318,87319,87321,87323,87325],{"class":117,"line":118},[115,87320,2001],{"class":262},[115,87322,3480],{"class":132},[115,87324,3483],{"class":132},[115,87326,67791],{"class":132},[115,87328,87329,87331,87333,87335],{"class":117,"line":136},[115,87330,2001],{"class":262},[115,87332,3480],{"class":132},[115,87334,1984],{"class":132},[115,87336,67791],{"class":132},[16,87338,42059,87339,87341,87342,87344],{},[20,87340,86386],{}," only if you changed the unit file itself, not when you only updated the ",[20,87343,2089],{}," contents.",[16,87346,87347],{},"Do not put secrets directly into a committed unit file if your deployment process can avoid it.",[52,87349,87351],{"id":87350},"docker-compose-example","Docker Compose example",[106,87353,87355],{"className":2485,"code":87354,"language":2487,"meta":111,"style":111},"services:\n  web:\n    environment:\n      SENTRY_DSN: ${SENTRY_DSN}\n      SENTRY_ENVIRONMENT: production\n      SENTRY_RELEASE: ${GIT_SHA}\n",[20,87356,87357,87363,87369,87375,87385,87394],{"__ignoreMap":111},[115,87358,87359,87361],{"class":117,"line":118},[115,87360,2495],{"class":2494},[115,87362,2498],{"class":125},[115,87364,87365,87367],{"class":117,"line":136},[115,87366,2503],{"class":2494},[115,87368,2498],{"class":125},[115,87370,87371,87373],{"class":117,"line":149},[115,87372,27844],{"class":2494},[115,87374,2498],{"class":125},[115,87376,87377,87380,87382],{"class":117,"line":162},[115,87378,87379],{"class":2494},"      SENTRY_DSN",[115,87381,2513],{"class":125},[115,87383,87384],{"class":132},"${SENTRY_DSN}\n",[115,87386,87387,87390,87392],{"class":117,"line":175},[115,87388,87389],{"class":2494},"      SENTRY_ENVIRONMENT",[115,87391,2513],{"class":125},[115,87393,27849],{"class":132},[115,87395,87396,87399,87401],{"class":117,"line":350},[115,87397,87398],{"class":2494},"      SENTRY_RELEASE",[115,87400,2513],{"class":125},[115,87402,87403],{"class":132},"${GIT_SHA}\n",[16,87405,87406,87407,87409],{},"Load ",[20,87408,86690],{}," from your deployment environment or secret store, not from a checked-in file containing production secrets.",[52,87411,87413],{"id":87412},"cicd-injection","CI\u002FCD injection",[16,87415,87416],{},"In CI\u002FCD, set:",[63,87418,87419,87424,87429],{},[66,87420,87421,87423],{},[20,87422,86690],{}," as a secret",[66,87425,87426,87428],{},[20,87427,86693],{}," per environment",[66,87430,87431,87433],{},[20,87432,86696],{}," from git SHA, tag, or build ID",[16,87435,87436],{},"This keeps the same code across staging and production while changing only environment-specific values.",[16,87438,87439,87441],{},[1226,87440,3515],{}," after deploy, verify the running service definition, rendered container configuration, or application behavior shows the expected values are in use.",[11,87443,87445],{"id":87444},"_5-verify-sentry-is-working-after-deploy","5) Verify Sentry is working after deploy",[16,87447,87448],{},"Do not assume the integration works just because the app starts.",[16,87450,87451],{},"One practical check is a temporary test view:",[106,87453,87455],{"className":2369,"code":87454,"language":1114,"meta":111,"style":111},"from django.http import HttpResponse\n\ndef sentry_test_error(request):\n    raise RuntimeError(\"Sentry test error from production verification\")\n",[20,87456,87457,87467,87471,87480],{"__ignoreMap":111},[115,87458,87459,87461,87463,87465],{"class":117,"line":118},[115,87460,5621],{"class":121},[115,87462,17240],{"class":125},[115,87464,5613],{"class":121},[115,87466,17245],{"class":125},[115,87468,87469],{"class":117,"line":136},[115,87470,310],{"emptyLinePlaceholder":309},[115,87472,87473,87475,87478],{"class":117,"line":149},[115,87474,8808],{"class":121},[115,87476,87477],{"class":262}," sentry_test_error",[115,87479,17271],{"class":125},[115,87481,87482,87484,87486,87488,87491],{"class":117,"line":162},[115,87483,37616],{"class":121},[115,87485,37619],{"class":202},[115,87487,37145],{"class":125},[115,87489,87490],{"class":132},"\"Sentry test error from production verification\"",[115,87492,2394],{"class":125},[16,87494,87495],{},"Add a temporary URL:",[106,87497,87499],{"className":2369,"code":87498,"language":1114,"meta":111,"style":111},"from django.urls import path\nfrom .views import sentry_test_error\n\nurlpatterns = [\n    path(\"sentry-test-error\u002F\", sentry_test_error),\n]\n",[20,87500,87501,87511,87522,87526,87534,87544],{"__ignoreMap":111},[115,87502,87503,87505,87507,87509],{"class":117,"line":118},[115,87504,5621],{"class":121},[115,87506,17252],{"class":125},[115,87508,5613],{"class":121},[115,87510,17257],{"class":125},[115,87512,87513,87515,87517,87519],{"class":117,"line":136},[115,87514,5621],{"class":121},[115,87516,53674],{"class":125},[115,87518,5613],{"class":121},[115,87520,87521],{"class":125}," sentry_test_error\n",[115,87523,87524],{"class":117,"line":149},[115,87525,310],{"emptyLinePlaceholder":309},[115,87527,87528,87530,87532],{"class":117,"line":162},[115,87529,17302],{"class":125},[115,87531,129],{"class":121},[115,87533,3540],{"class":125},[115,87535,87536,87538,87541],{"class":117,"line":175},[115,87537,17311],{"class":125},[115,87539,87540],{"class":132},"\"sentry-test-error\u002F\"",[115,87542,87543],{"class":125},", sentry_test_error),\n",[115,87545,87546],{"class":117,"line":350},[115,87547,2552],{"class":125},[16,87549,87550],{},"Deploy, then request the endpoint once from a controlled source only. Do not leave the route exposed longer than necessary. If possible, trigger it from an internal IP, VPN, or another restricted path and remove it immediately after verification.",[16,87552,87553],{},"You should get a 500 response. Check Sentry for a new event in the expected project and environment.",[16,87555,87556],{},"Validation points:",[63,87558,87559,87562,87565,87568,87571],{},[66,87560,87561],{},"event appears in the correct Sentry project",[66,87563,87564],{},"environment is labeled correctly",[66,87566,87567],{},"release is attached if configured",[66,87569,87570],{},"normal application requests still work",[66,87572,87573],{},"application startup and worker startup were unaffected",[16,87575,87576],{},"After validation, remove the test route and redeploy.",[16,87578,87579],{},"If adding a temporary failing route is not acceptable in your environment, use a controlled one-time verification path in code that calls Sentry directly during a maintenance window, then remove it after the event is confirmed.",[16,87581,87582,87584,87585,87587],{},[1226,87583,47125],{}," if verification reveals startup issues or excessive noise, clear ",[20,87586,86690],{}," and restart the app while you investigate.",[11,87589,87591],{"id":87590},"_6-configure-alerting-and-ownership","6) Configure alerting and ownership",[16,87593,87594],{},"Basic alerting is enough at first. Configure alerts for:",[63,87596,87597,87600,87603],{},[66,87598,87599],{},"new issues",[66,87601,87602],{},"regressions",[66,87604,87605],{},"high-frequency errors",[16,87607,87608],{},"Send alerts to the team channel that actually handles deploys and incidents. Email-only routing often gets ignored in small teams.",[16,87610,87611],{},"During initial rollout, keep alerts conservative. A noisy first week usually means:",[63,87613,87614,87617,87620],{},[66,87615,87616],{},"expected exceptions need filtering",[66,87618,87619],{},"staging and production are mixed together",[66,87621,87622],{},"probing traffic is generating avoidable errors",[11,87624,87626],{"id":87625},"_7-cover-worker-and-background-job-processes","7) Cover worker and background-job processes",[16,87628,87629],{},"If you run Celery or other worker processes, do not stop at the web service. Workers need the same Sentry environment variables and the same initialization code path, or task failures may never appear in the expected project and release.",[16,87631,87632],{},"In practice, that means:",[63,87634,87635,87640,87645,87650],{},[66,87636,87637,87638],{},"worker processes should run with the same ",[20,87639,86690],{},[66,87641,87642,87643],{},"worker processes should use the same ",[20,87644,86693],{},[66,87646,87647,87648],{},"worker processes should receive the same ",[20,87649,86696],{},[66,87651,87652],{},"the Django settings module used by workers must also initialize Sentry",[16,87654,87655,87656,87659],{},"For example, if your web service and Celery worker both load the same Django settings module, the settings-based ",[20,87657,87658],{},"sentry_sdk.init(...)"," block above will apply to both, as long as the worker process gets the same environment variables.",[16,87661,87662,87664],{},[1226,87663,3515],{}," after deploy, run or trigger a controlled failing background job and confirm the event appears in Sentry with the correct environment and release.",[11,87666,87668],{"id":87667},"_8-operational-notes-for-production-django-deployments","8) Operational notes for production Django deployments",[16,87670,87671,87672,87674],{},"Sentry is useful for exceptions, stack traces, release correlation, and issue grouping. It does ",[1226,87673,7474],{}," replace:",[63,87676,87677,87680,87683,87686,87688],{},[66,87678,87679],{},"web server access logs",[66,87681,87682],{},"reverse proxy logs",[66,87684,87685],{},"structured application logs",[66,87687,73886],{},[66,87689,87690],{},"infrastructure metrics",[16,87692,87693],{},"After deploys and migrations, watch both Sentry and your normal logs. Some failures appear as repeated exceptions in Sentry, while others show up first as startup failures, migration locks, timeout behavior, or upstream 502\u002F504 errors in your proxy.",[52,87695,87697],{"id":87696},"when-to-turn-this-into-reusable-automation","When to turn this into reusable automation",[16,87699,87700],{},"Once you repeat this setup across multiple Django services, the manual steps become good candidates for a template or deploy script. The settings block, env variable layout, release naming, and post-deploy verification are usually the first parts worth standardizing. Keep the manual path clear first so the automation reflects a known-good deployment.",[11,87702,1321],{"id":1320},[16,87704,87705],{},"This Django Sentry integration works because the Sentry SDK hooks into Django’s exception handling and reports unhandled errors with request context, environment labels, and release metadata.",[16,87707,87708],{},"A few production-safe defaults matter:",[63,87710,87711,87717,87723,87729],{},[66,87712,87713,87716],{},[20,87714,87715],{},"if SENTRY_DSN:"," keeps Sentry optional and makes rollback easy",[66,87718,87719,87722],{},[20,87720,87721],{},"send_default_pii=False"," avoids collecting user-identifying data unless you intentionally enable it",[66,87724,87725,87728],{},[20,87726,87727],{},"traces_sample_rate=0.0"," disables performance monitoring until you choose and tune it",[66,87730,87731],{},"environment variables keep secrets and environment-specific values out of source control",[16,87733,87734,87735,87737],{},"Use separate environments consistently. If staging sends events as ",[20,87736,27252],{},", issue triage becomes confusing quickly. If your app has high traffic or frequent bot noise, add filtering before enabling more features.",[11,87739,1337],{"id":1336},[63,87741,87742,87750,87756,87766,87772,87778,87784,87790],{},[66,87743,87744,87747,87748,211],{},[1226,87745,87746],{},"Multiple settings modules:"," add the Sentry block to the production settings path actually used by ",[20,87749,5074],{},[66,87751,87752,87755],{},[1226,87753,87754],{},"Staging vs production:"," separate projects can reduce confusion, but one project with distinct environment labels also works if your team is disciplined.",[66,87757,87758,87761,87762,87765],{},[1226,87759,87760],{},"Privacy and client IPs:"," review proxy and header handling before enabling PII collection. Do not turn on ",[20,87763,87764],{},"send_default_pii=True"," casually.",[66,87767,87768,87771],{},[1226,87769,87770],{},"High-volume error storms:"," filtering expected exceptions is usually better than broad sampling for basic error monitoring.",[66,87773,87774,87777],{},[1226,87775,87776],{},"Self-hosted Sentry:"," valid operationally, but you must maintain upgrades, storage, and reliability yourself.",[66,87779,87780,87783],{},[1226,87781,87782],{},"Gunicorn\u002FUvicorn timeouts and proxy errors:"," Sentry may show application exceptions, but upstream timeout behavior still needs app server and reverse proxy logs for full diagnosis.",[66,87785,87786,87789],{},[1226,87787,87788],{},"Migrations and startup failures:"," if the app fails before handling requests, you may see little or nothing in Sentry. Always keep direct access to service logs.",[66,87791,87792,87795,87796,87798],{},[1226,87793,87794],{},"Rollback priority:"," in an incident, disable Sentry by clearing ",[20,87797,86690],{}," in the runtime environment and restarting or redeploying. That is safer than making emergency code edits.",[11,87800,1386],{"id":1385},[16,87802,87803],{},"For related deployment work, see:",[63,87805,87806,87810,87815,87819],{},[66,87807,87808],{},[1395,87809,3000],{"href":2999},[66,87811,87812],{},[1395,87813,87814],{"href":3006},"Set Django Environment Variables in Production",[66,87816,87817],{},[1395,87818,2986],{"href":2985},[66,87820,87821],{},[1395,87822,87823],{"href":35631},"How to Debug Django 500 Errors in Production",[11,87825,1420],{"id":1419},[52,87827,87829],{"id":87828},"should-i-enable-sentry-in-development","Should I enable Sentry in development?",[16,87831,87832],{},"Usually no, or at least not by default in the same project as production. Local development already gives you direct tracebacks and logs. Staging is usually the better place to validate the production Sentry workflow.",[52,87834,87836],{"id":87835},"does-sentry-replace-django-logs","Does Sentry replace Django logs?",[16,87838,87839],{},"No. Sentry is for exception tracking and issue management. You still need application logs, process logs, and reverse proxy logs for operational debugging.",[52,87841,87843],{"id":87842},"how-do-i-keep-dsn-and-environment-values-secure","How do I keep DSN and environment values secure?",[16,87845,87846,87847,87849],{},"Store them in environment variables, secret managers, CI\u002FCD secrets, or protected env files. Do not hardcode them in ",[20,87848,10342],{}," or commit production secret files to version control.",[52,87851,87853],{"id":87852},"can-i-use-sentry-with-celery-and-background-jobs","Can I use Sentry with Celery and background jobs?",[16,87855,87856],{},"Yes, but test worker processes explicitly. Make sure worker services receive the same Sentry environment variables and load the same initialization path as the web application.",[52,87858,87860],{"id":87859},"what-should-i-do-if-sentry-generates-too-many-events","What should I do if Sentry generates too many events?",[16,87862,87863,87864,87866],{},"First, identify the noisy exception classes or endpoints and filter expected errors. Also verify that staging and production are separated correctly and that hostile probing traffic is not creating avoidable issue volume. If necessary, disable Sentry quickly by clearing ",[20,87865,86690],{}," while you adjust the configuration.",[1485,87868,87869],{},"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 .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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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":111,"searchDepth":149,"depth":149,"links":87871},[87872,87873,87874,87875,87876,87877,87878,87883,87884,87885,87886,87889,87890,87891,87892],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":86714,"depth":136,"text":86715},{"id":86813,"depth":136,"text":86814},{"id":86871,"depth":136,"text":86872},{"id":87257,"depth":136,"text":87258,"children":87879},[87880,87881,87882],{"id":87261,"depth":149,"text":87262},{"id":87350,"depth":149,"text":87351},{"id":87412,"depth":149,"text":87413},{"id":87444,"depth":136,"text":87445},{"id":87590,"depth":136,"text":87591},{"id":87625,"depth":136,"text":87626},{"id":87667,"depth":136,"text":87668,"children":87887},[87888],{"id":87696,"depth":149,"text":87697},{"id":1320,"depth":136,"text":1321},{"id":1336,"depth":136,"text":1337},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":87893},[87894,87895,87896,87897,87898],{"id":87828,"depth":149,"text":87829},{"id":87835,"depth":149,"text":87836},{"id":87842,"depth":149,"text":87843},{"id":87852,"depth":149,"text":87853},{"id":87859,"depth":149,"text":87860},"Local logs are not enough once a Django app is in production. Exceptions can happen after deploys, during background jobs, or only under real user traffic patterns.",{},"\u002Fsetup-sentry-for-django-production",[37876,1551,71135],{"title":86649,"description":87899},[1557,87905],"sentry","setup-sentry-for-django-production",[1557,87905],"Wlz4zaZAxFHBh26urvLawlhPua7gtrZCck_6PVAaZ8Q",{"id":58623,"title":58624,"body":87910,"category":3088,"description":60116,"difficulty":3090,"extension":1544,"funnel_stage":4545,"intent":1546,"meta":89104,"navigation":309,"path":60118,"priority":60119,"related":89105,"role":1553,"section":3098,"seo":89106,"stack":89107,"stem":60124,"tags":89108,"type":1561,"__hash__":60126},{"type":8,"value":87911,"toc":89038},[87912,87914,87918,87922,87924,87926,87928,87950,87952,87954,87956,87958,87960,88006,88010,88040,88042,88060,88062,88064,88066,88068,88070,88088,88090,88094,88096,88110,88112,88116,88118,88120,88126,88132,88134,88146,88148,88160,88162,88176,88178,88180,88192,88194,88206,88210,88212,88214,88216,88220,88222,88432,88434,88438,88474,88478,88482,88484,88496,88498,88500,88502,88506,88508,88522,88524,88526,88528,88532,88536,88550,88552,88576,88578,88580,88582,88596,88598,88600,88602,88604,88606,88620,88622,88634,88636,88680,88684,88686,88688,88708,88712,88716,88718,88720,88722,88724,88728,88730,88732,88734,88736,88758,88760,88776,88778,88790,88792,88794,88796,88806,88808,88810,88812,88814,88820,88822,88824,88844,88846,88848,88850,88854,88856,88858,88860,88862,88894,88896,88904,88906,88908,88910,88916,88918,88920,88922,88938,88940,88942,88944,88946,88948,88950,88952,88954,88958,88960,88962,88964,88968,88970,88972,88976,88980,88984,88988,88992,88994,88996,89002,89008,89010,89012,89014,89020,89024,89028,89030,89032,89034,89036],[11,87913,14],{"id":13},[16,87915,58631,87916,58634],{},[20,87917,10632],{},[16,87919,58637,87920,58641],{},[1226,87921,58640],{},[16,87923,58644],{},[11,87925,30],{"id":29},[16,87927,58649],{},[1173,87929,87930,87932,87934,87938,87940,87942,87944,87948],{},[66,87931,58654],{},[66,87933,58657],{},[66,87935,58660,87936,58663],{},[20,87937,10632],{},[66,87939,58666],{},[66,87941,58669],{},[66,87943,58672],{},[66,87945,58675,87946,58678],{},[20,87947,10296],{},[66,87949,58681],{},[23099,87951],{},[11,87953,43],{"id":42},[11,87955,58689],{"id":58688},[52,87957,58693],{"id":58692},[16,87959,58696],{},[106,87961,87962],{"className":11064,"code":58699,"language":11066,"meta":111,"style":111},[20,87963,87964,87974,87990],{"__ignoreMap":111},[115,87965,87966,87968,87970,87972],{"class":117,"line":118},[115,87967,14611],{"class":121},[115,87969,14614],{"class":121},[115,87971,58710],{"class":262},[115,87973,3811],{"class":125},[115,87975,87976,87978,87980,87982,87984,87986,87988],{"class":117,"line":136},[115,87977,14611],{"class":121},[115,87979,14625],{"class":121},[115,87981,58721],{"class":262},[115,87983,14631],{"class":121},[115,87985,14634],{"class":121},[115,87987,58728],{"class":132},[115,87989,3811],{"class":125},[115,87991,87992,87994,87996,87998,88000,88002,88004],{"class":117,"line":149},[115,87993,14709],{"class":121},[115,87995,58737],{"class":121},[115,87997,58740],{"class":121},[115,87999,14614],{"class":121},[115,88001,58745],{"class":125},[115,88003,14659],{"class":121},[115,88005,58750],{"class":125},[16,88007,58753,88008,58757],{},[20,88009,58756],{},[106,88011,88012],{"className":11064,"code":58760,"language":11066,"meta":111,"style":111},[20,88013,88014,88018,88022],{"__ignoreMap":111},[115,88015,88016],{"class":117,"line":118},[115,88017,58767],{"class":125},[115,88019,88020],{"class":117,"line":136},[115,88021,310],{"emptyLinePlaceholder":309},[115,88023,88024,88026,88028,88030,88032,88034,88036,88038],{"class":117,"line":149},[115,88025,14709],{"class":121},[115,88027,58778],{"class":125},[115,88029,14611],{"class":121},[115,88031,58740],{"class":121},[115,88033,58785],{"class":121},[115,88035,58788],{"class":125},[115,88037,14659],{"class":121},[115,88039,58750],{"class":125},[16,88041,58795],{},[106,88043,88044],{"className":11064,"code":58798,"language":11066,"meta":111,"style":111},[20,88045,88046],{"__ignoreMap":111},[115,88047,88048,88050,88052,88054,88056,88058],{"class":117,"line":118},[115,88049,14644],{"class":121},[115,88051,14614],{"class":121},[115,88053,58745],{"class":125},[115,88055,58811],{"class":121},[115,88057,58814],{"class":121},[115,88059,58750],{"class":125},[16,88061,58819],{},[52,88063,58823],{"id":58822},[16,88065,58826],{},[52,88067,58830],{"id":58829},[16,88069,58833],{},[63,88071,88072,88074,88078,88080,88082,88084,88086],{},[66,88073,58838],{},[66,88075,58841,88076],{},[20,88077,58844],{},[66,88079,10386],{},[66,88081,10389],{},[66,88083,10392],{},[66,88085,58853],{},[66,88087,58856],{},[16,88089,58859],{},[16,88091,88092],{},[1226,88093,36435],{},[16,88095,58866],{},[106,88097,88098],{"className":108,"code":58869,"language":110,"meta":111,"style":111},[20,88099,88100],{"__ignoreMap":111},[115,88101,88102,88104,88106,88108],{"class":117,"line":118},[115,88103,5352],{"class":262},[115,88105,5355],{"class":202},[115,88107,167],{"class":132},[115,88109,47178],{"class":202},[16,88111,58884],{},[16,88113,58887,88114,58891],{},[20,88115,58890],{},[23099,88117],{},[11,88119,58897],{"id":58896},[52,88121,58901,88122,4493,88124,58906],{"id":58900},[20,88123,10542],{},[20,88125,10587],{},[16,88127,58909,88128,58912,88130,58915],{},[20,88129,10542],{},[20,88131,10587],{},[16,88133,58918],{},[106,88135,88136],{"className":108,"code":58921,"language":110,"meta":111,"style":111},[20,88137,88138],{"__ignoreMap":111},[115,88139,88140,88142,88144],{"class":117,"line":118},[115,88141,8618],{"class":262},[115,88143,6600],{"class":132},[115,88145,10561],{"class":132},[16,88147,3488],{},[106,88149,88150],{"className":108,"code":58936,"language":110,"meta":111,"style":111},[20,88151,88152],{"__ignoreMap":111},[115,88153,88154,88156,88158],{"class":117,"line":118},[115,88155,8618],{"class":262},[115,88157,6600],{"class":132},[115,88159,58947],{"class":132},[52,88161,58951],{"id":58950},[106,88163,88164],{"className":108,"code":58954,"language":110,"meta":111,"style":111},[20,88165,88166],{"__ignoreMap":111},[115,88167,88168,88170,88172,88174],{"class":117,"line":118},[115,88169,8618],{"class":262},[115,88171,16323],{"class":132},[115,88173,604],{"class":121},[115,88175,12353],{"class":132},[16,88177,58969],{},[52,88179,58973],{"id":58972},[106,88181,88182],{"className":108,"code":58976,"language":110,"meta":111,"style":111},[20,88183,88184],{"__ignoreMap":111},[115,88185,88186,88188,88190],{"class":117,"line":118},[115,88187,1114],{"class":262},[115,88189,1024],{"class":202},[115,88191,10604],{"class":132},[16,88193,58989],{},[106,88195,88196],{"className":108,"code":58992,"language":110,"meta":111,"style":111},[20,88197,88198],{"__ignoreMap":111},[115,88199,88200,88202,88204],{"class":117,"line":118},[115,88201,1114],{"class":262},[115,88203,1024],{"class":202},[115,88205,10618],{"class":132},[16,88207,88208],{},[1226,88209,36435],{},[16,88211,59009],{},[23099,88213],{},[11,88215,59015],{"id":59014},[52,88217,59019,88218,59022],{"id":59018},[20,88219,10632],{},[16,88221,59025],{},[106,88223,88224],{"className":2369,"code":59028,"language":1114,"meta":111,"style":111},[20,88225,88226,88232,88236,88244,88256,88268,88272,88278,88290,88296,88308,88312,88320,88326,88336,88346,88356,88366,88376,88390,88408,88418,88424,88428],{"__ignoreMap":111},[115,88227,88228,88230],{"class":117,"line":118},[115,88229,5613],{"class":121},[115,88231,5616],{"class":125},[115,88233,88234],{"class":117,"line":136},[115,88235,310],{"emptyLinePlaceholder":309},[115,88237,88238,88240,88242],{"class":117,"line":149},[115,88239,59045],{"class":125},[115,88241,129],{"class":121},[115,88243,54396],{"class":125},[115,88245,88246,88248,88250,88252,88254],{"class":117,"line":162},[115,88247,59054],{"class":125},[115,88249,129],{"class":121},[115,88251,8884],{"class":125},[115,88253,10787],{"class":132},[115,88255,2394],{"class":125},[115,88257,88258,88260,88262,88264,88266],{"class":117,"line":175},[115,88259,59067],{"class":125},[115,88261,129],{"class":121},[115,88263,8884],{"class":125},[115,88265,10826],{"class":132},[115,88267,2394],{"class":125},[115,88269,88270],{"class":117,"line":350},[115,88271,310],{"emptyLinePlaceholder":309},[115,88273,88274,88276],{"class":117,"line":365},[115,88275,10833],{"class":121},[115,88277,59086],{"class":125},[115,88279,88280,88282,88284,88286,88288],{"class":117,"line":380},[115,88281,59091],{"class":125},[115,88283,59094],{"class":132},[115,88285,10861],{"class":125},[115,88287,129],{"class":121},[115,88289,59101],{"class":125},[115,88291,88292,88294],{"class":117,"line":487},[115,88293,10833],{"class":121},[115,88295,59108],{"class":125},[115,88297,88298,88300,88302,88304,88306],{"class":117,"line":2095},[115,88299,59091],{"class":125},[115,88301,10858],{"class":132},[115,88303,10861],{"class":125},[115,88305,129],{"class":121},[115,88307,59121],{"class":125},[115,88309,88310],{"class":117,"line":2104},[115,88311,310],{"emptyLinePlaceholder":309},[115,88313,88314,88316,88318],{"class":117,"line":2113},[115,88315,10632],{"class":202},[115,88317,2380],{"class":121},[115,88319,2166],{"class":125},[115,88321,88322,88324],{"class":117,"line":2122},[115,88323,10664],{"class":132},[115,88325,3374],{"class":125},[115,88327,88328,88330,88332,88334],{"class":117,"line":2131},[115,88329,10671],{"class":132},[115,88331,2513],{"class":125},[115,88333,10676],{"class":132},[115,88335,3354],{"class":125},[115,88337,88338,88340,88342,88344],{"class":117,"line":2136},[115,88339,10683],{"class":132},[115,88341,10686],{"class":125},[115,88343,10689],{"class":132},[115,88345,3430],{"class":125},[115,88347,88348,88350,88352,88354],{"class":117,"line":2142},[115,88349,10696],{"class":132},[115,88351,10686],{"class":125},[115,88353,10701],{"class":132},[115,88355,3430],{"class":125},[115,88357,88358,88360,88362,88364],{"class":117,"line":2273},[115,88359,10708],{"class":132},[115,88361,10686],{"class":125},[115,88363,10713],{"class":132},[115,88365,3430],{"class":125},[115,88367,88368,88370,88372,88374],{"class":117,"line":2282},[115,88369,10720],{"class":132},[115,88371,10686],{"class":125},[115,88373,10725],{"class":132},[115,88375,3430],{"class":125},[115,88377,88378,88380,88382,88384,88386,88388],{"class":117,"line":2291},[115,88379,10732],{"class":132},[115,88381,10735],{"class":125},[115,88383,10738],{"class":132},[115,88385,1153],{"class":125},[115,88387,10743],{"class":132},[115,88389,10746],{"class":125},[115,88391,88392,88394,88396,88398,88400,88402,88404,88406],{"class":117,"line":2299},[115,88393,10751],{"class":132},[115,88395,2513],{"class":125},[115,88397,10756],{"class":202},[115,88399,10759],{"class":125},[115,88401,10762],{"class":132},[115,88403,1153],{"class":125},[115,88405,10767],{"class":132},[115,88407,10770],{"class":125},[115,88409,88410,88412,88414,88416],{"class":117,"line":2307},[115,88411,59226],{"class":132},[115,88413,2513],{"class":125},[115,88415,35949],{"class":202},[115,88417,3354],{"class":125},[115,88419,88420,88422],{"class":117,"line":2315},[115,88421,10775],{"class":132},[115,88423,59239],{"class":125},[115,88425,88426],{"class":117,"line":2320},[115,88427,2233],{"class":125},[115,88429,88430],{"class":117,"line":7083},[115,88431,2323],{"class":125},[52,88433,59251],{"id":59250},[16,88435,59254,88436,59257],{},[20,88437,10342],{},[106,88439,88440],{"className":21525,"code":59260,"language":21527,"meta":111,"style":111},[20,88441,88442,88446,88450,88454,88458,88462,88466,88470],{"__ignoreMap":111},[115,88443,88444],{"class":117,"line":118},[115,88445,59267],{},[115,88447,88448],{"class":117,"line":136},[115,88449,59272],{},[115,88451,88452],{"class":117,"line":149},[115,88453,12444],{},[115,88455,88456],{"class":117,"line":162},[115,88457,59281],{},[115,88459,88460],{"class":117,"line":175},[115,88461,12454],{},[115,88463,88464],{"class":117,"line":350},[115,88465,59290],{},[115,88467,88468],{"class":117,"line":365},[115,88469,59295],{},[115,88471,88472],{"class":117,"line":380},[115,88473,2338],{},[16,88475,59302,88476,59305],{},[20,88477,191],{},[52,88479,59309,88480,59312],{"id":59308},[20,88481,10933],{},[16,88483,59315],{},[63,88485,88486,88490],{},[66,88487,88488,59322],{},[20,88489,10282],{},[66,88491,88492,37824,88494,59331],{},[20,88493,59327],{},[20,88495,59330],{},[16,88497,59334],{},[52,88499,59338],{"id":59337},[16,88501,59341],{},[16,88503,88504],{},[1226,88505,36435],{},[16,88507,33361],{},[106,88509,88510],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,88511,88512],{"__ignoreMap":111},[115,88513,88514,88516,88518,88520],{"class":117,"line":118},[115,88515,1114],{"class":262},[115,88517,1117],{"class":132},[115,88519,1814],{"class":132},[115,88521,1817],{"class":202},[23099,88523],{},[11,88525,59367],{"id":59366},[52,88527,59371],{"id":59370},[16,88529,59374,88530,59377],{},[20,88531,1277],{},[16,88533,9761,88534,59382],{},[20,88535,1277],{},[106,88537,88538],{"className":2026,"code":46951,"language":2028,"meta":111,"style":111},[20,88539,88540,88544],{"__ignoreMap":111},[115,88541,88542],{"class":117,"line":118},[115,88543,2060],{"class":262},[115,88545,88546,88548],{"class":117,"line":136},[115,88547,2089],{"class":121},[115,88549,4912],{"class":125},[16,88551,59399],{},[106,88553,88554],{"className":108,"code":59402,"language":110,"meta":111,"style":111},[20,88555,88556,88566],{"__ignoreMap":111},[115,88557,88558,88560,88562,88564],{"class":117,"line":118},[115,88559,2001],{"class":262},[115,88561,6733],{"class":132},[115,88563,59413],{"class":132},[115,88565,23352],{"class":132},[115,88567,88568,88570,88572,88574],{"class":117,"line":136},[115,88569,2001],{"class":262},[115,88571,12480],{"class":132},[115,88573,12483],{"class":202},[115,88575,23352],{"class":132},[16,88577,59428],{},[52,88579,59432],{"id":59431},[16,88581,59435],{},[63,88583,88584,88588,88592,88594],{},[66,88585,88586],{},[20,88587,10342],{},[66,88589,59444,88590,59447],{},[20,88591,191],{},[66,88593,59450],{},[66,88595,59453],{},[52,88597,59457],{"id":59456},[16,88599,59460],{},[23099,88601],{},[11,88603,59466],{"id":59465},[52,88605,59470],{"id":59469},[106,88607,88608],{"className":108,"code":3248,"language":110,"meta":111,"style":111},[20,88609,88610],{"__ignoreMap":111},[115,88611,88612,88614,88616,88618],{"class":117,"line":118},[115,88613,1114],{"class":262},[115,88615,1117],{"class":132},[115,88617,1814],{"class":132},[115,88619,1817],{"class":202},[52,88621,59488],{"id":59487},[106,88623,88624],{"className":108,"code":6059,"language":110,"meta":111,"style":111},[20,88625,88626],{"__ignoreMap":111},[115,88627,88628,88630,88632],{"class":117,"line":118},[115,88629,1114],{"class":262},[115,88631,1117],{"class":132},[115,88633,6070],{"class":132},[16,88635,5144],{},[106,88637,88638],{"className":2369,"code":59505,"language":1114,"meta":111,"style":111},[20,88639,88640,88650,88656,88666,88674],{"__ignoreMap":111},[115,88641,88642,88644,88646,88648],{"class":117,"line":118},[115,88643,5621],{"class":121},[115,88645,11218],{"class":125},[115,88647,5613],{"class":121},[115,88649,11223],{"class":125},[115,88651,88652,88654],{"class":117,"line":136},[115,88653,6102],{"class":202},[115,88655,59524],{"class":125},[115,88657,88658,88660,88662,88664],{"class":117,"line":149},[115,88659,11232],{"class":121},[115,88661,11235],{"class":125},[115,88663,5719],{"class":121},[115,88665,11240],{"class":125},[115,88667,88668,88670,88672],{"class":117,"line":162},[115,88669,11245],{"class":125},[115,88671,11248],{"class":132},[115,88673,2394],{"class":125},[115,88675,88676,88678],{"class":117,"line":175},[115,88677,11255],{"class":202},[115,88679,11258],{"class":125},[16,88681,59551,88682,59554],{},[20,88683,1558],{},[52,88685,59558],{"id":59557},[16,88687,59561],{},[106,88689,88690],{"className":108,"code":59564,"language":110,"meta":111,"style":111},[20,88691,88692],{"__ignoreMap":111},[115,88693,88694,88696,88698,88700,88702,88704,88706],{"class":117,"line":118},[115,88695,2001],{"class":262},[115,88697,5030],{"class":132},[115,88699,2788],{"class":202},[115,88701,2791],{"class":132},[115,88703,2794],{"class":202},[115,88705,2797],{"class":202},[115,88707,2800],{"class":202},[16,88709,59585,88710,59588],{},[20,88711,14954],{},[16,88713,88714],{},[1226,88715,4956],{},[16,88717,59595],{},[23099,88719],{},[11,88721,59601],{"id":59600},[52,88723,59605],{"id":59604},[16,88725,59608,88726,59611],{},[20,88727,38156],{},[16,88729,59614],{},[52,88731,59618],{"id":59617},[16,88733,59621],{},[52,88735,59625],{"id":59624},[106,88737,88738],{"className":108,"code":59628,"language":110,"meta":111,"style":111},[20,88739,88740,88748],{"__ignoreMap":111},[115,88741,88742,88744,88746],{"class":117,"line":118},[115,88743,1114],{"class":262},[115,88745,1117],{"class":132},[115,88747,1129],{"class":132},[115,88749,88750,88752,88754,88756],{"class":117,"line":136},[115,88751,1114],{"class":262},[115,88753,1117],{"class":132},[115,88755,1826],{"class":132},[115,88757,1841],{"class":202},[16,88759,59651],{},[106,88761,88762],{"className":108,"code":59654,"language":110,"meta":111,"style":111},[20,88763,88764],{"__ignoreMap":111},[115,88765,88766,88768,88770,88772,88774],{"class":117,"line":118},[115,88767,1114],{"class":262},[115,88769,1117],{"class":132},[115,88771,1826],{"class":132},[115,88773,59667],{"class":202},[115,88775,59670],{"class":202},[52,88777,59674],{"id":59673},[106,88779,88780],{"className":108,"code":11330,"language":110,"meta":111,"style":111},[20,88781,88782],{"__ignoreMap":111},[115,88783,88784,88786,88788],{"class":117,"line":118},[115,88785,1114],{"class":262},[115,88787,1117],{"class":132},[115,88789,1129],{"class":132},[16,88791,59689],{},[52,88793,59693],{"id":59692},[16,88795,59696],{},[1173,88797,88798,88800,88802,88804],{},[66,88799,59701],{},[66,88801,1186],{},[66,88803,59706],{},[66,88805,58162],{},[16,88807,59711],{},[23099,88809],{},[11,88811,59717],{"id":59716},[52,88813,59721],{"id":59720},[16,88815,59724,88816,59728,88818,59732],{},[20,88817,59727],{},[20,88819,59731],{},[52,88821,59736],{"id":59735},[16,88823,59739],{},[1173,88825,88826,88828,88830,88832,88834,88836,88838,88840,88842],{},[66,88827,59744],{},[66,88829,59747],{},[66,88831,59750],{},[66,88833,59753],{},[66,88835,59756],{},[66,88837,59759],{},[66,88839,1186],{},[66,88841,59764],{},[66,88843,59767],{},[52,88845,59771],{"id":59770},[16,88847,59774],{},[52,88849,11436],{"id":11435},[16,88851,59779,88852,59782],{},[20,88853,59727],{},[23099,88855],{},[11,88857,59788],{"id":59787},[52,88859,59792],{"id":59791},[16,88861,59795],{},[106,88863,88864],{"className":2369,"code":59798,"language":1114,"meta":111,"style":111},[20,88865,88866,88876],{"__ignoreMap":111},[115,88867,88868,88870,88872,88874],{"class":117,"line":118},[115,88869,5621],{"class":121},[115,88871,59807],{"class":125},[115,88873,5613],{"class":121},[115,88875,59812],{"class":125},[115,88877,88878,88880,88882,88884,88886,88888,88890,88892],{"class":117,"line":136},[115,88879,6102],{"class":202},[115,88881,59819],{"class":125},[115,88883,10632],{"class":202},[115,88885,10844],{"class":125},[115,88887,10847],{"class":132},[115,88889,10850],{"class":125},[115,88891,59830],{"class":132},[115,88893,53835],{"class":125},[16,88895,59835],{},[106,88897,88898],{"className":2369,"code":59838,"language":1114,"meta":111,"style":111},[20,88899,88900],{"__ignoreMap":111},[115,88901,88902],{"class":117,"line":118},[115,88903,59838],{"class":125},[52,88905,59848],{"id":59847},[16,88907,59851],{},[52,88909,59855],{"id":59854},[16,88911,59858,88912,59861,88914,59865],{},[20,88913,10933],{},[20,88915,59864],{},[23099,88917],{},[11,88919,1321],{"id":1320},[16,88921,59872],{},[63,88923,88924,88926,88928,88930,88932,88934,88936],{},[66,88925,59877],{},[66,88927,59880],{},[66,88929,59883],{},[66,88931,59886],{},[66,88933,59889],{},[66,88935,59892],{},[66,88937,59895],{},[16,88939,59898],{},[23099,88941],{},[11,88943,10095],{"id":10094},[52,88945,59906],{"id":59905},[16,88947,59909],{},[52,88949,59913],{"id":59912},[16,88951,59916],{},[52,88953,59920],{"id":59919},[16,88955,59923,88956,59926],{},[20,88957,47853],{},[52,88959,59930],{"id":59929},[16,88961,59933],{},[52,88963,59937],{"id":59936},[16,88965,59940,88966,59943],{},[20,88967,1277],{},[23099,88969],{},[11,88971,1386],{"id":1385},[16,88973,44637,88974,211],{},[1395,88975,59952],{"href":3006},[16,88977,59955,88978,211],{},[1395,88979,2986],{"href":2985},[16,88981,59960,88982,211],{},[1395,88983,8039],{"href":8038},[16,88985,59965,88986,211],{},[1395,88987,59968],{"href":1409},[16,88989,59971,88990,211],{},[1395,88991,11531],{"href":6333},[23099,88993],{},[11,88995,1420],{"id":1419},[52,88997,1434,88998,4493,89000,59985],{"id":59980},[20,88999,10542],{},[20,89001,10546],{},[16,89003,59988,89004,59991,89006,59994],{},[20,89005,10542],{},[20,89007,10587],{},[52,89009,59998],{"id":59997},[16,89011,60001],{},[52,89013,60005],{"id":60004},[16,89015,60008,89016,60011,89018,60014],{},[20,89017,10282],{},[20,89019,10286],{},[52,89021,60018,89022,60021],{"id":60017},[20,89023,38156],{},[16,89025,60024,89026,60027],{},[20,89027,10296],{},[52,89029,60031],{"id":60030},[16,89031,60034],{},[52,89033,60038],{"id":60037},[16,89035,60041],{},[1485,89037,60044],{},{"title":111,"searchDepth":149,"depth":149,"links":89039},[89040,89041,89042,89043,89048,89053,89059,89064,89069,89076,89082,89087,89088,89095,89096],{"id":13,"depth":136,"text":14},{"id":29,"depth":136,"text":30},{"id":42,"depth":136,"text":43},{"id":58688,"depth":136,"text":58689,"children":89044},[89045,89046,89047],{"id":58692,"depth":149,"text":58693},{"id":58822,"depth":149,"text":58823},{"id":58829,"depth":149,"text":58830},{"id":58896,"depth":136,"text":58897,"children":89049},[89050,89051,89052],{"id":58900,"depth":149,"text":60058},{"id":58950,"depth":149,"text":58951},{"id":58972,"depth":149,"text":58973},{"id":59014,"depth":136,"text":59015,"children":89054},[89055,89056,89057,89058],{"id":59018,"depth":149,"text":60064},{"id":59250,"depth":149,"text":59251},{"id":59308,"depth":149,"text":60067},{"id":59337,"depth":149,"text":59338},{"id":59366,"depth":136,"text":59367,"children":89060},[89061,89062,89063],{"id":59370,"depth":149,"text":59371},{"id":59431,"depth":149,"text":59432},{"id":59456,"depth":149,"text":59457},{"id":59465,"depth":136,"text":59466,"children":89065},[89066,89067,89068],{"id":59469,"depth":149,"text":59470},{"id":59487,"depth":149,"text":59488},{"id":59557,"depth":149,"text":59558},{"id":59600,"depth":136,"text":59601,"children":89070},[89071,89072,89073,89074,89075],{"id":59604,"depth":149,"text":59605},{"id":59617,"depth":149,"text":59618},{"id":59624,"depth":149,"text":59625},{"id":59673,"depth":149,"text":59674},{"id":59692,"depth":149,"text":59693},{"id":59716,"depth":136,"text":59717,"children":89077},[89078,89079,89080,89081],{"id":59720,"depth":149,"text":59721},{"id":59735,"depth":149,"text":59736},{"id":59770,"depth":149,"text":59771},{"id":11435,"depth":149,"text":11436},{"id":59787,"depth":136,"text":59788,"children":89083},[89084,89085,89086],{"id":59791,"depth":149,"text":59792},{"id":59847,"depth":149,"text":59848},{"id":59854,"depth":149,"text":59855},{"id":1320,"depth":136,"text":1321},{"id":10094,"depth":136,"text":10095,"children":89089},[89090,89091,89092,89093,89094],{"id":59905,"depth":149,"text":59906},{"id":59912,"depth":149,"text":59913},{"id":59919,"depth":149,"text":59920},{"id":59929,"depth":149,"text":59930},{"id":59936,"depth":149,"text":59937},{"id":1385,"depth":136,"text":1386},{"id":1419,"depth":136,"text":1420,"children":89097},[89098,89099,89100,89101,89102,89103],{"id":59980,"depth":149,"text":60109},{"id":59997,"depth":149,"text":59998},{"id":60004,"depth":149,"text":60005},{"id":60017,"depth":149,"text":60113},{"id":60030,"depth":149,"text":60031},{"id":60037,"depth":149,"text":60038},{},[11637,60121,2992],{"title":58624,"description":60116},[1557,1558],[1557,1558],1777116327044]