[{"data":1,"prerenderedAt":1435},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fconfigure-qgis-environment-variables-and-profiles":3},{"id":4,"title":5,"body":6,"description":1425,"extension":1426,"meta":1427,"navigation":226,"path":1431,"seo":1432,"stem":1433,"__hash__":1434},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fconfigure-qgis-environment-variables-and-profiles\u002Findex.md","Configure QGIS Environment Variables and Profiles",{"type":7,"value":8,"toc":1410},"minimark",[9,13,17,26,137,142,152,156,180,288,299,303,306,355,416,465,486,490,493,613,640,644,651,747,879,889,893,896,961,985,995,999,1002,1049,1058,1062,1065,1240,1249,1253,1277,1281,1317,1321,1330,1334,1343,1355,1365,1374,1378,1406],[10,11,5],"h1",{"id":12},"configure-qgis-environment-variables-and-profiles",[14,15,16],"p",{},"The same script can behave differently on two machines for reasons that have nothing to do with the code: a different set of installed plugins, a CRS default in the user's settings, a proxy configured in one place and not the other, PROJ finding different datum grids. QGIS draws its behaviour from two places outside the script — the user profile, which holds settings, plugins and styles, and the environment, which tells QGIS where its libraries and data are and how to run. Controlling both is what makes scripts reproducible on desktops, servers and in CI.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","Headless QGIS & Server Automation",". It explains user profiles and how to create and select them, the environment variables that matter for PyQGIS, startup scripts per profile, and how to give servers and containers a clean, known configuration.",[14,27,28],{},[29,30,35,39,43,50,59,69,75,81,85,89,94,99,102,105,108,111,114,119,125,129,133],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 262","img","Three sources of QGIS behaviour — environment variables, the user profile and the script — with the first two pinned for reproducibility","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Where QGIS behaviour comes from",[40,41,42],"desc",{},"Three sources shape a QGIS process. Environment variables, set before start, locate libraries and data and choose the display platform: QGIS_PREFIX_PATH, QT_QPA_PLATFORM, PROJ_DATA, GDAL options. The user profile holds settings, plugins, styles, authentication and the startup script. The script itself sets anything else in code. Reproducible runs pin the first two.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","262","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Environment, profile, then code",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","56","222","190","8","#eff3ff","#2563eb","stroke-width:2",[51,70,74],{"x":71,"y":72,"style":73,"fill":67,"textAnchor":57},"135","98.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","environment",[51,76,80],{"x":71,"y":77,"style":78,"fill":79,"textAnchor":57},"126.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","#2f3b35","QGIS_PREFIX_PATH",[51,82,84],{"x":71,"y":83,"style":78,"fill":79,"textAnchor":57},"154.6","QT_QPA_PLATFORM",[51,86,88],{"x":71,"y":87,"style":78,"fill":79,"textAnchor":57},"182.6","PROJ_DATA · GDAL_*",[51,90,93],{"x":71,"y":91,"style":78,"fill":92,"textAnchor":57},"210.6","#59645f","set before start",[44,95],{"x":96,"y":62,"width":63,"height":64,"rx":65,"fill":97,"stroke":98,"style":68},"269","#eef7f4","#0f766e",[51,100,101],{"x":53,"y":72,"style":73,"fill":98,"textAnchor":57},"user profile",[51,103,104],{"x":53,"y":77,"style":78,"fill":79,"textAnchor":57},"settings (QGIS3.ini)",[51,106,107],{"x":53,"y":83,"style":78,"fill":79,"textAnchor":57},"plugins, styles",[51,109,110],{"x":53,"y":87,"style":78,"fill":79,"textAnchor":57},"auth, startup.py",[51,112,113],{"x":53,"y":91,"style":78,"fill":92,"textAnchor":57},"per profile folder",[44,115],{"x":116,"y":62,"width":63,"height":64,"rx":65,"fill":117,"stroke":118,"style":68},"514","#e8efe6","#15803d",[51,120,124],{"x":121,"y":122,"style":73,"fill":123,"textAnchor":57},"625","112.6","#166534","script",[51,126,128],{"x":121,"y":127,"style":78,"fill":79,"textAnchor":57},"140.6","QgsApplication(…)",[51,130,132],{"x":121,"y":131,"style":78,"fill":79,"textAnchor":57},"168.6","QgsSettings",[51,134,136],{"x":121,"y":135,"style":78,"fill":92,"textAnchor":57},"196.6","explicit in code",[138,139,141],"h2",{"id":140},"prerequisites","Prerequisites",[143,144,145,149],"ul",{},[146,147,148],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[146,150,151],{},"Access to the shell or service configuration where scripts are launched — a terminal, a systemd unit, a scheduled task, a Dockerfile.",[138,153,155],{"id":154},"understand-user-profiles","Understand user profiles",[14,157,158,159,163,164,167,168,171,172,175,176,179],{},"A profile is a folder containing everything QGIS stores per user: ",[160,161,162],"code",{},"QGIS\u002FQGIS3.ini"," with settings, ",[160,165,166],{},"python\u002Fplugins"," with installed plugins, ",[160,169,170],{},"processing"," with scripts and models, ",[160,173,174],{},"qgis-auth.db"," with credentials, ",[160,177,178],{},"symbology-style.db"," with styles. Profiles live under the QGIS settings directory; their location differs by platform.",[181,182,187],"pre",{"className":183,"code":184,"language":185,"meta":186,"style":186},"language-python shiki shiki-themes github-dark","from qgis.core import QgsApplication\nfrom pathlib import Path\n\nprint(\"active profile folder:\", QgsApplication.qgisSettingsDirPath())\nprofiles_root = Path(QgsApplication.qgisSettingsDirPath()).parent\nprint(\"profiles:\", [p.name for p in profiles_root.iterdir() if p.is_dir()])\n","python","",[160,188,189,208,221,228,245,257],{"__ignoreMap":186},[190,191,194,198,202,205],"span",{"class":192,"line":193},"line",1,[190,195,197],{"class":196},"snl16","from",[190,199,201],{"class":200},"s95oV"," qgis.core ",[190,203,204],{"class":196},"import",[190,206,207],{"class":200}," QgsApplication\n",[190,209,211,213,216,218],{"class":192,"line":210},2,[190,212,197],{"class":196},[190,214,215],{"class":200}," pathlib ",[190,217,204],{"class":196},[190,219,220],{"class":200}," Path\n",[190,222,224],{"class":192,"line":223},3,[190,225,227],{"emptyLinePlaceholder":226},true,"\n",[190,229,231,235,238,242],{"class":192,"line":230},4,[190,232,234],{"class":233},"sDLfK","print",[190,236,237],{"class":200},"(",[190,239,241],{"class":240},"sU2Wk","\"active profile folder:\"",[190,243,244],{"class":200},", QgsApplication.qgisSettingsDirPath())\n",[190,246,248,251,254],{"class":192,"line":247},5,[190,249,250],{"class":200},"profiles_root ",[190,252,253],{"class":196},"=",[190,255,256],{"class":200}," Path(QgsApplication.qgisSettingsDirPath()).parent\n",[190,258,260,262,264,267,270,273,276,279,282,285],{"class":192,"line":259},6,[190,261,234],{"class":233},[190,263,237],{"class":200},[190,265,266],{"class":240},"\"profiles:\"",[190,268,269],{"class":200},", [p.name ",[190,271,272],{"class":196},"for",[190,274,275],{"class":200}," p ",[190,277,278],{"class":196},"in",[190,280,281],{"class":200}," profiles_root.iterdir() ",[190,283,284],{"class":196},"if",[190,286,287],{"class":200}," p.is_dir()])\n",[14,289,290,294,295,298],{},[291,292,293],"strong",{},"Breakdown:"," ",[160,296,297],{},"qgisSettingsDirPath"," returns the active profile's folder, and its parent holds all profiles. Separate profiles let one person keep a clean configuration for production work next to an experimental one with development plugins, or give a shared machine one profile per task. Because plugins and settings are per profile, a script run \"in QGIS\" can behave differently depending on which profile was active — a common cause of \"it works on my machine\".",[138,300,302],{"id":301},"start-qgis-with-a-specific-profile","Start QGIS with a specific profile",[14,304,305],{},"Desktop QGIS and standalone scripts can both be pointed at a profile explicitly.",[14,307,308],{},[29,309,312,315,318,321,324,329,334,339,342,345,349,352],{"viewBox":310,"role":32,"ariaLabel":311,"xmlns":34},"0 0 760 206","Selecting a profile by command-line options for QGIS Desktop or by settings path for standalone scripts",[36,313,314],{},"Choosing a profile",[40,316,317],{},"QGIS Desktop accepts --profile and --profiles-path on the command line to start with a named profile in a chosen folder. Standalone scripts pass a profile folder to QgsApplication, or set QGIS_CUSTOM_CONFIG_PATH, so that settings, plugins and authentication come from that folder instead of the user's default profile.",[44,319],{"x":46,"y":46,"width":47,"height":320,"fill":49},"206",[51,322,323],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Desktop flags, or a settings path in code",[44,325],{"x":61,"y":326,"width":327,"height":328,"rx":65,"fill":66,"stroke":67,"style":68},"60","340","130",[51,330,333],{"x":331,"y":332,"style":73,"fill":67,"textAnchor":57},"194","102.6","QGIS Desktop",[51,335,338],{"x":331,"y":336,"style":337,"fill":79,"textAnchor":57},"128.6","text-anchor:middle;font-size:9.5px;font-family:monospace","qgis --profile automation",[51,340,341],{"x":331,"y":83,"style":337,"fill":79,"textAnchor":57},"--profiles-path \u002Fopt\u002Fqgis-profiles",[44,343],{"x":344,"y":326,"width":327,"height":328,"rx":65,"fill":97,"stroke":98,"style":68},"396",[51,346,348],{"x":347,"y":332,"style":73,"fill":98,"textAnchor":57},"566","standalone script",[51,350,351],{"x":347,"y":336,"style":337,"fill":79,"textAnchor":57},"QgsApplication([], False,",[51,353,354],{"x":347,"y":83,"style":337,"fill":79,"textAnchor":57},"  '\u002Fopt\u002Fqgis-profiles\u002Fautomation')",[181,356,360],{"className":357,"code":358,"language":359,"meta":186,"style":186},"language-bash shiki shiki-themes github-dark","# desktop: a named profile from a shared folder\nqgis --profiles-path \u002Fopt\u002Fqgis-profiles --profile automation\n\n# headless script: point QGIS at a dedicated configuration folder\nexport QGIS_CUSTOM_CONFIG_PATH=\u002Fopt\u002Fqgis-profiles\u002Fautomation\npython3 \u002Fopt\u002Fscripts\u002Fnightly_export.py\n","bash",[160,361,362,368,386,390,395,408],{"__ignoreMap":186},[190,363,364],{"class":192,"line":193},[190,365,367],{"class":366},"sjoCn","# desktop: a named profile from a shared folder\n",[190,369,370,374,377,380,383],{"class":192,"line":210},[190,371,373],{"class":372},"svObZ","qgis",[190,375,376],{"class":233}," --profiles-path",[190,378,379],{"class":240}," \u002Fopt\u002Fqgis-profiles",[190,381,382],{"class":233}," --profile",[190,384,385],{"class":240}," automation\n",[190,387,388],{"class":192,"line":223},[190,389,227],{"emptyLinePlaceholder":226},[190,391,392],{"class":192,"line":230},[190,393,394],{"class":366},"# headless script: point QGIS at a dedicated configuration folder\n",[190,396,397,400,403,405],{"class":192,"line":247},[190,398,399],{"class":196},"export",[190,401,402],{"class":200}," QGIS_CUSTOM_CONFIG_PATH",[190,404,253],{"class":196},[190,406,407],{"class":200},"\u002Fopt\u002Fqgis-profiles\u002Fautomation\n",[190,409,410,413],{"class":192,"line":259},[190,411,412],{"class":372},"python3",[190,414,415],{"class":240}," \u002Fopt\u002Fscripts\u002Fnightly_export.py\n",[181,417,419],{"className":183,"code":418,"language":185,"meta":186,"style":186},"from qgis.core import QgsApplication\nqgs = QgsApplication([], False, \"\u002Fopt\u002Fqgis-profiles\u002Fautomation\")\nqgs.initQgis()\nprint(QgsApplication.qgisSettingsDirPath())\n",[160,420,421,431,453,458],{"__ignoreMap":186},[190,422,423,425,427,429],{"class":192,"line":193},[190,424,197],{"class":196},[190,426,201],{"class":200},[190,428,204],{"class":196},[190,430,207],{"class":200},[190,432,433,436,438,441,444,447,450],{"class":192,"line":210},[190,434,435],{"class":200},"qgs ",[190,437,253],{"class":196},[190,439,440],{"class":200}," QgsApplication([], ",[190,442,443],{"class":233},"False",[190,445,446],{"class":200},", ",[190,448,449],{"class":240},"\"\u002Fopt\u002Fqgis-profiles\u002Fautomation\"",[190,451,452],{"class":200},")\n",[190,454,455],{"class":192,"line":223},[190,456,457],{"class":200},"qgs.initQgis()\n",[190,459,460,462],{"class":192,"line":230},[190,461,234],{"class":233},[190,463,464],{"class":200},"(QgsApplication.qgisSettingsDirPath())\n",[14,466,467,469,470,473,474,477,478,481,482,485],{},[291,468,293],{}," On the desktop, ",[160,471,472],{},"--profiles-path"," chooses the folder that contains profiles and ",[160,475,476],{},"--profile"," the profile inside it, so a shortcut or launcher can start QGIS in a known configuration. For scripts, the third argument of the ",[160,479,480],{},"QgsApplication"," constructor sets the configuration path directly; ",[160,483,484],{},"QGIS_CUSTOM_CONFIG_PATH"," does the same through the environment. Pointing automation at its own profile isolates it from whatever plugins and settings the interactive user has.",[138,487,489],{"id":488},"set-the-environment-variables-that-matter","Set the environment variables that matter",[14,491,492],{},"A handful of environment variables decide whether a standalone PyQGIS process starts at all and how it behaves.",[181,494,496],{"className":357,"code":495,"language":359,"meta":186,"style":186},"# where QGIS is installed (prefix containing lib\u002F and share\u002Fqgis\u002F)\nexport QGIS_PREFIX_PATH=\u002Fusr\n# headless Qt: no display needed\nexport QT_QPA_PLATFORM=offscreen\n# PROJ data and grids, shared and pinned\nexport PROJ_DATA=\u002Fopt\u002Fproj-data:\u002Fusr\u002Fshare\u002Fproj\n# GDAL behaviour: cache size, network timeouts, no AUX files beside rasters\nexport GDAL_CACHEMAX=1024\nexport GDAL_HTTP_TIMEOUT=60\nexport GDAL_PAM_ENABLED=NO\n# Python path to the QGIS bindings (Linux system install)\nexport PYTHONPATH=\u002Fusr\u002Fshare\u002Fqgis\u002Fpython:$PYTHONPATH\n",[160,497,498,503,515,520,532,537,549,555,568,581,594,600],{"__ignoreMap":186},[190,499,500],{"class":192,"line":193},[190,501,502],{"class":366},"# where QGIS is installed (prefix containing lib\u002F and share\u002Fqgis\u002F)\n",[190,504,505,507,510,512],{"class":192,"line":210},[190,506,399],{"class":196},[190,508,509],{"class":200}," QGIS_PREFIX_PATH",[190,511,253],{"class":196},[190,513,514],{"class":200},"\u002Fusr\n",[190,516,517],{"class":192,"line":223},[190,518,519],{"class":366},"# headless Qt: no display needed\n",[190,521,522,524,527,529],{"class":192,"line":230},[190,523,399],{"class":196},[190,525,526],{"class":200}," QT_QPA_PLATFORM",[190,528,253],{"class":196},[190,530,531],{"class":200},"offscreen\n",[190,533,534],{"class":192,"line":247},[190,535,536],{"class":366},"# PROJ data and grids, shared and pinned\n",[190,538,539,541,544,546],{"class":192,"line":259},[190,540,399],{"class":196},[190,542,543],{"class":200}," PROJ_DATA",[190,545,253],{"class":196},[190,547,548],{"class":200},"\u002Fopt\u002Fproj-data:\u002Fusr\u002Fshare\u002Fproj\n",[190,550,552],{"class":192,"line":551},7,[190,553,554],{"class":366},"# GDAL behaviour: cache size, network timeouts, no AUX files beside rasters\n",[190,556,558,560,563,565],{"class":192,"line":557},8,[190,559,399],{"class":196},[190,561,562],{"class":200}," GDAL_CACHEMAX",[190,564,253],{"class":196},[190,566,567],{"class":233},"1024\n",[190,569,571,573,576,578],{"class":192,"line":570},9,[190,572,399],{"class":196},[190,574,575],{"class":200}," GDAL_HTTP_TIMEOUT",[190,577,253],{"class":196},[190,579,580],{"class":233},"60\n",[190,582,584,586,589,591],{"class":192,"line":583},10,[190,585,399],{"class":196},[190,587,588],{"class":200}," GDAL_PAM_ENABLED",[190,590,253],{"class":196},[190,592,593],{"class":200},"NO\n",[190,595,597],{"class":192,"line":596},11,[190,598,599],{"class":366},"# Python path to the QGIS bindings (Linux system install)\n",[190,601,603,605,608,610],{"class":192,"line":602},12,[190,604,399],{"class":196},[190,606,607],{"class":200}," PYTHONPATH",[190,609,253],{"class":196},[190,611,612],{"class":200},"\u002Fusr\u002Fshare\u002Fqgis\u002Fpython:$PYTHONPATH\n",[14,614,615,294,617,619,620,622,623,626,627,630,631,634,635,639],{},[291,616,293],{},[160,618,80],{}," tells ",[160,621,480],{}," where providers and resources live; scripts can also call ",[160,624,625],{},"QgsApplication.setPrefixPath"," in code. ",[160,628,629],{},"QT_QPA_PLATFORM=offscreen"," lets QGIS start without a display server — essential on servers, in cron jobs and in containers. ",[160,632,633],{},"PROJ_DATA"," pins the coordinate transformation data and grids, so results agree across machines, as explained in ",[21,636,638],{"href":637},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Finstall-proj-datum-grids-pyqgis\u002F","installing PROJ datum grids",". GDAL variables tune caching, network behaviour and sidecar files. On Windows, the OSGeo4W environment batch files set the equivalents.",[138,641,643],{"id":642},"use-a-startup-script-per-profile","Use a startup script per profile",[14,645,646,647,650],{},"Each profile can run a ",[160,648,649],{},"startup.py"," when QGIS starts — the place for project-independent setup such as registering custom expression functions, setting defaults or adding paths.",[14,652,653],{},[29,654,657,660,663,666,681,684,690,694,700,703,708,711,715,718,721,724,728,732,735,739,743],{"viewBox":655,"role":32,"ariaLabel":656,"xmlns":34},"0 0 760 196","The QGIS startup sequence with the profile's startup.py running after settings load and before plugins and projects",[36,658,659],{},"Startup script order",[40,661,662],{},"When QGIS starts it initialises the application, loads the profile's settings, then runs startup.py from the profile's python folder, then loads plugins, then opens any project. Code in startup.py can therefore register expression functions and set defaults before plugins and projects need them.",[44,664],{"x":46,"y":46,"width":47,"height":665,"fill":49},"196",[667,668,669],"defs",{},[670,671,677],"marker",{"id":672,"viewBox":673,"refX":65,"refY":674,"markerWidth":675,"markerHeight":675,"orient":676},"envStartupArrow","0 0 10 10","5","7","auto-start-reverse",[678,679],"path",{"d":680,"fill":79},"M0 0 L10 5 L0 10 z",[51,682,683],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Before plugins, before projects",[44,685],{"x":61,"y":686,"width":687,"height":688,"rx":65,"fill":689,"stroke":92,"style":68},"70","140","110","#fffdf7",[51,691,693],{"x":692,"y":336,"style":73,"fill":56,"textAnchor":57},"94","init app",[192,695],{"x1":696,"y1":697,"x2":698,"y2":697,"stroke":79,"style":699},"164","125","184","stroke-width:1.8;marker-end:url(#envStartupArrow)",[44,701],{"x":702,"y":686,"width":687,"height":688,"rx":65,"fill":689,"stroke":92,"style":68},"188",[51,704,707],{"x":705,"y":706,"style":73,"fill":56,"textAnchor":57},"258","116.6","load profile",[51,709,710],{"x":705,"y":127,"style":78,"fill":92,"textAnchor":57},"settings",[192,712],{"x1":713,"y1":697,"x2":714,"y2":697,"stroke":79,"style":699},"328","348",[44,716],{"x":717,"y":686,"width":687,"height":688,"rx":65,"fill":97,"stroke":98,"style":68},"352",[51,719,649],{"x":720,"y":706,"style":73,"fill":98,"textAnchor":57},"422",[51,722,723],{"x":720,"y":127,"style":78,"fill":92,"textAnchor":57},"your setup",[192,725],{"x1":726,"y1":697,"x2":727,"y2":697,"stroke":79,"style":699},"492","512",[44,729],{"x":730,"y":686,"width":731,"height":688,"rx":65,"fill":689,"stroke":92,"style":68},"516","100",[51,733,734],{"x":347,"y":336,"style":73,"fill":56,"textAnchor":57},"plugins",[192,736],{"x1":737,"y1":697,"x2":738,"y2":697,"stroke":79,"style":699},"616","636",[44,740],{"x":741,"y":686,"width":742,"height":688,"rx":65,"fill":689,"stroke":92,"style":68},"640","96",[51,744,746],{"x":745,"y":336,"style":73,"fill":56,"textAnchor":57},"688","project",[181,748,750],{"className":183,"code":749,"language":185,"meta":186,"style":186},"# \u003Cprofile>\u002Fpython\u002Fstartup.py\nfrom qgis.core import QgsSettings, QgsExpressionContextUtils\n\ns = QgsSettings()\ns.setValue(\"app\u002Fprojections\u002FunknownCrsBehavior\", \"PromptUserForCrs\")\ns.setValue(\"Processing\u002FConfiguration\u002FINVALID_GEOMETRIES\", 1)   # skip invalid, do not stop\nQgsExpressionContextUtils.setGlobalVariable(\"organisation_name\", \"City Planning Office\")\n\ntry:\n    from org_expressions import register_all          # site package with custom functions\n    register_all()\nexcept ImportError:\n    pass\n",[160,751,752,757,768,772,782,797,815,830,834,842,858,863,873],{"__ignoreMap":186},[190,753,754],{"class":192,"line":193},[190,755,756],{"class":366},"# \u003Cprofile>\u002Fpython\u002Fstartup.py\n",[190,758,759,761,763,765],{"class":192,"line":210},[190,760,197],{"class":196},[190,762,201],{"class":200},[190,764,204],{"class":196},[190,766,767],{"class":200}," QgsSettings, QgsExpressionContextUtils\n",[190,769,770],{"class":192,"line":223},[190,771,227],{"emptyLinePlaceholder":226},[190,773,774,777,779],{"class":192,"line":230},[190,775,776],{"class":200},"s ",[190,778,253],{"class":196},[190,780,781],{"class":200}," QgsSettings()\n",[190,783,784,787,790,792,795],{"class":192,"line":247},[190,785,786],{"class":200},"s.setValue(",[190,788,789],{"class":240},"\"app\u002Fprojections\u002FunknownCrsBehavior\"",[190,791,446],{"class":200},[190,793,794],{"class":240},"\"PromptUserForCrs\"",[190,796,452],{"class":200},[190,798,799,801,804,806,809,812],{"class":192,"line":259},[190,800,786],{"class":200},[190,802,803],{"class":240},"\"Processing\u002FConfiguration\u002FINVALID_GEOMETRIES\"",[190,805,446],{"class":200},[190,807,808],{"class":233},"1",[190,810,811],{"class":200},")   ",[190,813,814],{"class":366},"# skip invalid, do not stop\n",[190,816,817,820,823,825,828],{"class":192,"line":551},[190,818,819],{"class":200},"QgsExpressionContextUtils.setGlobalVariable(",[190,821,822],{"class":240},"\"organisation_name\"",[190,824,446],{"class":200},[190,826,827],{"class":240},"\"City Planning Office\"",[190,829,452],{"class":200},[190,831,832],{"class":192,"line":557},[190,833,227],{"emptyLinePlaceholder":226},[190,835,836,839],{"class":192,"line":570},[190,837,838],{"class":196},"try",[190,840,841],{"class":200},":\n",[190,843,844,847,850,852,855],{"class":192,"line":583},[190,845,846],{"class":196},"    from",[190,848,849],{"class":200}," org_expressions ",[190,851,204],{"class":196},[190,853,854],{"class":200}," register_all          ",[190,856,857],{"class":366},"# site package with custom functions\n",[190,859,860],{"class":192,"line":596},[190,861,862],{"class":200},"    register_all()\n",[190,864,865,868,871],{"class":192,"line":602},[190,866,867],{"class":196},"except",[190,869,870],{"class":233}," ImportError",[190,872,841],{"class":200},[190,874,876],{"class":192,"line":875},13,[190,877,878],{"class":196},"    pass\n",[14,880,881,883,884,888],{},[291,882,293],{}," Settings written at startup apply to everything that follows in the session, so organisation-wide defaults are enforced without relying on each user's choices. Global variables set here are available to every project's expressions. Importing a shared module of custom expression functions makes them available before any project needs them. Keep startup scripts small and defensive — an exception here produces an error at every launch. The feature is covered from the user side in ",[21,885,887],{"href":886},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-python-at-qgis-startup-pyqgis\u002F","running Python at QGIS startup",".",[138,890,892],{"id":891},"provide-organisation-wide-defaults","Provide organisation-wide defaults",[14,894,895],{},"Administrators often want every user's QGIS to start with the same defaults — a proxy, a default CRS, approved plugin repositories — without touching individual profiles. QGIS reads a global settings file whose values act as defaults for any setting a profile has not changed.",[181,897,901],{"className":898,"code":899,"language":900,"meta":186,"style":186},"language-ini shiki shiki-themes github-dark","; qgis_global_settings.ini\n[proxy]\nproxyEnabled=true\nproxyHost=proxy.example.org\nproxyPort=8080\n\n[Projections]\ndefaultProjectCrs=EPSG:25832\n\n[app]\nplugin_repositories\\org\\url=https:\u002F\u002Fplugins.example.org\u002Fplugins.xml\nplugin_repositories\\org\\enabled=true\n","ini",[160,902,903,908,913,918,923,928,932,937,942,946,951,956],{"__ignoreMap":186},[190,904,905],{"class":192,"line":193},[190,906,907],{},"; qgis_global_settings.ini\n",[190,909,910],{"class":192,"line":210},[190,911,912],{},"[proxy]\n",[190,914,915],{"class":192,"line":223},[190,916,917],{},"proxyEnabled=true\n",[190,919,920],{"class":192,"line":230},[190,921,922],{},"proxyHost=proxy.example.org\n",[190,924,925],{"class":192,"line":247},[190,926,927],{},"proxyPort=8080\n",[190,929,930],{"class":192,"line":259},[190,931,227],{"emptyLinePlaceholder":226},[190,933,934],{"class":192,"line":551},[190,935,936],{},"[Projections]\n",[190,938,939],{"class":192,"line":557},[190,940,941],{},"defaultProjectCrs=EPSG:25832\n",[190,943,944],{"class":192,"line":570},[190,945,227],{"emptyLinePlaceholder":226},[190,947,948],{"class":192,"line":583},[190,949,950],{},"[app]\n",[190,952,953],{"class":192,"line":596},[190,954,955],{},"plugin_repositories\\org\\url=https:\u002F\u002Fplugins.example.org\u002Fplugins.xml\n",[190,957,958],{"class":192,"line":602},[190,959,960],{},"plugin_repositories\\org\\enabled=true\n",[181,962,964],{"className":357,"code":963,"language":359,"meta":186,"style":186},"export QGIS_GLOBAL_SETTINGS_FILE=\u002Fetc\u002Fqgis\u002Fqgis_global_settings.ini\nqgis   # or: qgis --globalsettingsfile \u002Fetc\u002Fqgis\u002Fqgis_global_settings.ini\n",[160,965,966,978],{"__ignoreMap":186},[190,967,968,970,973,975],{"class":192,"line":193},[190,969,399],{"class":196},[190,971,972],{"class":200}," QGIS_GLOBAL_SETTINGS_FILE",[190,974,253],{"class":196},[190,976,977],{"class":200},"\u002Fetc\u002Fqgis\u002Fqgis_global_settings.ini\n",[190,979,980,982],{"class":192,"line":210},[190,981,373],{"class":372},[190,983,984],{"class":366},"   # or: qgis --globalsettingsfile \u002Fetc\u002Fqgis\u002Fqgis_global_settings.ini\n",[14,986,987,989,990,994],{},[291,988,293],{}," Values in the global file are defaults: a user's own setting still wins, so the file sets sensible behaviour without locking anyone in. Pointing QGIS at it with an environment variable or command-line option means it can be deployed centrally, for example in a login script or a desktop launcher. Settings keys follow the same paths as in profiles; find them in the advanced settings editor. Combined with a private plugin repository, as in ",[21,991,993],{"href":992},"\u002Fqgis-plugin-development\u002Fpublishing-to-the-qgis-plugin-repository\u002Fhost-private-plugin-repository\u002F","hosting a private plugin repository",", this gives an organisation a consistent QGIS setup on every desktop.",[138,996,998],{"id":997},"make-servers-and-containers-reproducible","Make servers and containers reproducible",[14,1000,1001],{},"On servers and in CI, the goal is that every run starts from the same configuration. Baking a profile and environment into the image or service definition achieves that.",[181,1003,1007],{"className":1004,"code":1005,"language":1006,"meta":186,"style":186},"language-dockerfile shiki shiki-themes github-dark","FROM qgis\u002Fqgis:release-3_40\nENV QT_QPA_PLATFORM=offscreen \\\n    QGIS_CUSTOM_CONFIG_PATH=\u002Fopt\u002Fqgis-profile \\\n    PROJ_DATA=\u002Fopt\u002Fproj-data:\u002Fusr\u002Fshare\u002Fproj\nCOPY qgis-profile\u002F \u002Fopt\u002Fqgis-profile\u002F\nCOPY proj-data\u002F \u002Fopt\u002Fproj-data\u002F\nCOPY scripts\u002F \u002Fopt\u002Fscripts\u002F\nCMD [\"python3\", \"\u002Fopt\u002Fscripts\u002Fnightly_export.py\"]\n","dockerfile",[160,1008,1009,1014,1019,1024,1029,1034,1039,1044],{"__ignoreMap":186},[190,1010,1011],{"class":192,"line":193},[190,1012,1013],{},"FROM qgis\u002Fqgis:release-3_40\n",[190,1015,1016],{"class":192,"line":210},[190,1017,1018],{},"ENV QT_QPA_PLATFORM=offscreen \\\n",[190,1020,1021],{"class":192,"line":223},[190,1022,1023],{},"    QGIS_CUSTOM_CONFIG_PATH=\u002Fopt\u002Fqgis-profile \\\n",[190,1025,1026],{"class":192,"line":230},[190,1027,1028],{},"    PROJ_DATA=\u002Fopt\u002Fproj-data:\u002Fusr\u002Fshare\u002Fproj\n",[190,1030,1031],{"class":192,"line":247},[190,1032,1033],{},"COPY qgis-profile\u002F \u002Fopt\u002Fqgis-profile\u002F\n",[190,1035,1036],{"class":192,"line":259},[190,1037,1038],{},"COPY proj-data\u002F \u002Fopt\u002Fproj-data\u002F\n",[190,1040,1041],{"class":192,"line":551},[190,1042,1043],{},"COPY scripts\u002F \u002Fopt\u002Fscripts\u002F\n",[190,1045,1046],{"class":192,"line":557},[190,1047,1048],{},"CMD [\"python3\", \"\u002Fopt\u002Fscripts\u002Fnightly_export.py\"]\n",[14,1050,1051,1053,1054,888],{},[291,1052,293],{}," A pinned QGIS image, a profile folder with exactly the settings and plugins the scripts need, and pinned PROJ data make every container run identical — regardless of the host. Keeping the profile folder in version control means configuration changes are reviewed like code. The same principles apply to a systemd service or scheduled task: set the variables in the unit file, point at a dedicated profile, and never depend on an interactive user's settings. Container details are in ",[21,1055,1057],{"href":1056},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Frun-pyqgis-in-docker-container\u002F","running PyQGIS in a Docker container",[138,1059,1061],{"id":1060},"record-the-environment-with-every-run","Record the environment with every run",[14,1063,1064],{},"When results differ between runs, knowing the environment is half the diagnosis. A few lines at the start of a script log what matters.",[181,1066,1068],{"className":183,"code":1067,"language":185,"meta":186,"style":186},"import os, platform\nfrom qgis.core import Qgis, QgsApplication\nfrom osgeo import gdal\n\ninfo = {\n    \"qgis\": Qgis.version(), \"python\": platform.python_version(), \"gdal\": gdal.__version__,\n    \"profile\": QgsApplication.qgisSettingsDirPath(),\n    \"qt_platform\": os.environ.get(\"QT_QPA_PLATFORM\"),\n    \"proj_data\": os.environ.get(\"PROJ_DATA\") or os.environ.get(\"PROJ_LIB\"),\n}\nfor k, v in info.items():\n    print(f\"{k:\u003C12} {v}\")\n",[160,1069,1070,1077,1088,1100,1104,1114,1140,1148,1162,1186,1191,1203],{"__ignoreMap":186},[190,1071,1072,1074],{"class":192,"line":193},[190,1073,204],{"class":196},[190,1075,1076],{"class":200}," os, platform\n",[190,1078,1079,1081,1083,1085],{"class":192,"line":210},[190,1080,197],{"class":196},[190,1082,201],{"class":200},[190,1084,204],{"class":196},[190,1086,1087],{"class":200}," Qgis, QgsApplication\n",[190,1089,1090,1092,1095,1097],{"class":192,"line":223},[190,1091,197],{"class":196},[190,1093,1094],{"class":200}," osgeo ",[190,1096,204],{"class":196},[190,1098,1099],{"class":200}," gdal\n",[190,1101,1102],{"class":192,"line":230},[190,1103,227],{"emptyLinePlaceholder":226},[190,1105,1106,1109,1111],{"class":192,"line":247},[190,1107,1108],{"class":200},"info ",[190,1110,253],{"class":196},[190,1112,1113],{"class":200}," {\n",[190,1115,1116,1119,1122,1125,1128,1131,1134,1137],{"class":192,"line":259},[190,1117,1118],{"class":240},"    \"qgis\"",[190,1120,1121],{"class":200},": Qgis.version(), ",[190,1123,1124],{"class":240},"\"python\"",[190,1126,1127],{"class":200},": platform.python_version(), ",[190,1129,1130],{"class":240},"\"gdal\"",[190,1132,1133],{"class":200},": gdal.",[190,1135,1136],{"class":233},"__version__",[190,1138,1139],{"class":200},",\n",[190,1141,1142,1145],{"class":192,"line":551},[190,1143,1144],{"class":240},"    \"profile\"",[190,1146,1147],{"class":200},": QgsApplication.qgisSettingsDirPath(),\n",[190,1149,1150,1153,1156,1159],{"class":192,"line":557},[190,1151,1152],{"class":240},"    \"qt_platform\"",[190,1154,1155],{"class":200},": os.environ.get(",[190,1157,1158],{"class":240},"\"QT_QPA_PLATFORM\"",[190,1160,1161],{"class":200},"),\n",[190,1163,1164,1167,1169,1172,1175,1178,1181,1184],{"class":192,"line":570},[190,1165,1166],{"class":240},"    \"proj_data\"",[190,1168,1155],{"class":200},[190,1170,1171],{"class":240},"\"PROJ_DATA\"",[190,1173,1174],{"class":200},") ",[190,1176,1177],{"class":196},"or",[190,1179,1180],{"class":200}," os.environ.get(",[190,1182,1183],{"class":240},"\"PROJ_LIB\"",[190,1185,1161],{"class":200},[190,1187,1188],{"class":192,"line":583},[190,1189,1190],{"class":200},"}\n",[190,1192,1193,1195,1198,1200],{"class":192,"line":596},[190,1194,272],{"class":196},[190,1196,1197],{"class":200}," k, v ",[190,1199,278],{"class":196},[190,1201,1202],{"class":200}," info.items():\n",[190,1204,1205,1208,1210,1213,1216,1219,1222,1225,1228,1231,1234,1236,1238],{"class":192,"line":602},[190,1206,1207],{"class":233},"    print",[190,1209,237],{"class":200},[190,1211,1212],{"class":196},"f",[190,1214,1215],{"class":240},"\"",[190,1217,1218],{"class":233},"{",[190,1220,1221],{"class":200},"k",[190,1223,1224],{"class":196},":\u003C12",[190,1226,1227],{"class":233},"}",[190,1229,1230],{"class":233}," {",[190,1232,1233],{"class":200},"v",[190,1235,1227],{"class":233},[190,1237,1215],{"class":240},[190,1239,452],{"class":200},[14,1241,1242,1244,1245,888],{},[291,1243,293],{}," QGIS, Python and GDAL versions, the active profile and the key environment variables explain most differences between two runs. Writing this to the run log costs nothing; when a colleague reports a different result, comparing the two records usually finds the cause in seconds. Combine it with the logging setup in ",[21,1246,1248],{"href":1247},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fhandle-errors-and-logging-in-unattended-scripts\u002F","handling errors and logging in unattended scripts",[138,1250,1252],{"id":1251},"qgis-version-compatibility","QGIS version compatibility",[14,1254,1255,1256,1258,1259,446,1261,1263,1264,1266,1267,1269,1270,1272,1273,1276],{},"Profiles, ",[160,1257,476],{},"\u002F",[160,1260,472],{},[160,1262,484],{},", the ",[160,1265,480],{}," configuration-path argument and ",[160,1268,649],{}," are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. PROJ 9.1 and newer read ",[160,1271,633],{},", with ",[160,1274,1275],{},"PROJ_LIB"," accepted as an alias. Settings keys can change between versions; read them from the Options dialog's advanced settings editor on the version you target.",[138,1278,1280],{"id":1279},"troubleshooting","Troubleshooting",[143,1282,1283,1291,1303,1309],{},[146,1284,1285,294,1288,1290],{},[291,1286,1287],{},"\"Could not connect to display\".",[160,1289,629],{}," is not set for a headless run.",[146,1292,1293,294,1296,1298,1299,1302],{},[291,1294,1295],{},"Providers missing in a script.",[160,1297,80],{}," or ",[160,1300,1301],{},"setPrefixPath"," points at the wrong folder.",[146,1304,1305,1308],{},[291,1306,1307],{},"A plugin's behaviour appears in automation."," The script uses the interactive user's profile; give it its own.",[146,1310,1311,1314,1315,888],{},[291,1312,1313],{},"Coordinates differ between server and desktop."," Different PROJ data; pin ",[160,1316,633],{},[138,1318,1320],{"id":1319},"conclusion","Conclusion",[14,1322,1323,1324,1326,1327,1329],{},"Keep automation in its own user profile, select it with ",[160,1325,476],{}," or a configuration path, set the prefix path, offscreen Qt platform, PROJ and GDAL variables explicitly, put organisation-wide setup in the profile's ",[160,1328,649],{},", bake profile and environment into containers and services, and log the environment with every run.",[138,1331,1333],{"id":1332},"frequently-asked-questions","Frequently Asked Questions",[14,1335,1336,1339,1340,888],{},[291,1337,1338],{},"Can two profiles share plugins?","\nNot automatically; install plugins per profile, or point both at a shared plugin path with ",[160,1341,1342],{},"QGIS_PLUGINPATH",[14,1344,1345,1348,1349,1351,1352,1354],{},[291,1346,1347],{},"Where is QGIS3.ini?","\nIn the profile folder under ",[160,1350,162],{},"; prefer ",[160,1353,132],{}," in code to editing it directly.",[14,1356,1357,1360,1361,1364],{},[291,1358,1359],{},"Can I start QGIS with a different language?","\nYes — the ",[160,1362,1363],{},"--lang"," option, or the locale setting in the profile.",[14,1366,1367,1370,1371,888],{},[291,1368,1369],{},"Does QGIS Server use profiles?","\nNo; it is configured through its own environment variables such as ",[160,1372,1373],{},"QGIS_SERVER_LOG_FILE",[138,1375,1377],{"id":1376},"related","Related",[143,1379,1380,1385,1390,1396,1401],{},[146,1381,1382,1384],{},[21,1383,24],{"href":23}," — the guide this recipe belongs to",[146,1386,1387],{},[21,1388,1389],{"href":1056},"Run PyQGIS in a Docker Container",[146,1391,1392],{},[21,1393,1395],{"href":1394},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fschedule-pyqgis-scripts-with-cron\u002F","Schedule PyQGIS Scripts with Cron",[146,1397,1398],{},[21,1399,1400],{"href":886},"Run Python at QGIS Startup",[146,1402,1403],{},[21,1404,1405],{"href":637},"Install PROJ Datum Grids for PyQGIS",[1407,1408,1409],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":186,"searchDepth":210,"depth":210,"links":1411},[1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424],{"id":140,"depth":210,"text":141},{"id":154,"depth":210,"text":155},{"id":301,"depth":210,"text":302},{"id":488,"depth":210,"text":489},{"id":642,"depth":210,"text":643},{"id":891,"depth":210,"text":892},{"id":997,"depth":210,"text":998},{"id":1060,"depth":210,"text":1061},{"id":1251,"depth":210,"text":1252},{"id":1279,"depth":210,"text":1280},{"id":1319,"depth":210,"text":1320},{"id":1332,"depth":210,"text":1333},{"id":1376,"depth":210,"text":1377},"Control how QGIS and PyQGIS scripts start — user profiles for separate settings and plugins, environment variables such as QGIS_PREFIX_PATH, QT_QPA_PLATFORM, PROJ_DATA and GDAL options, startup scripts per profile, and reproducible settings for servers, containers and CI.","md",{"slug":12,"type":1428,"breadcrumb":1429,"datePublished":1430,"dateModified":1430},"article","Environment Variables and Profiles","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fconfigure-qgis-environment-variables-and-profiles",{"title":5,"description":1425},"pyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fconfigure-qgis-environment-variables-and-profiles\u002Findex","rPlUhMzzGZewVQ9RclUhl-rXIPBZrVdj1vwqGmsB8f4",1790966252950]