[{"data":1,"prerenderedAt":1390},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-qgis-with-python-bindings-on-linux":3},{"id":4,"title":5,"body":6,"description":1381,"extension":1382,"meta":1383,"navigation":260,"path":1386,"seo":1387,"stem":1388,"__hash__":1389},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-qgis-with-python-bindings-on-linux\u002Findex.md","Install QGIS with Python Bindings on Linux",{"type":7,"value":8,"toc":1366},"minimark",[9,13,26,35,154,159,169,173,176,180,186,378,401,405,408,473,549,575,579,586,694,708,712,715,823,833,837,840,888,982,995,999,1002,1180,1206,1210,1213,1217,1255,1259,1268,1272,1278,1287,1296,1310,1324,1330,1334,1362],[10,11,5],"h1",{"id":12},"install-qgis-with-python-bindings-on-linux",[14,15,16,17,21,22,25],"p",{},"On Linux, QGIS's Python bindings are part of the distribution's package system rather than a self-contained installer, which is both an advantage — ",[18,19,20],"code",{},"python3"," can import ",[18,23,24],{},"qgis.core"," directly, no wrapper scripts needed — and a source of confusion: distribution repositories often carry old versions, packages are split in non-obvious ways, and installing extra Python packages with pip can collide with the system. A few deliberate choices avoid nearly all of it.",[14,27,28,29,34],{},"This recipe belongs to ",[30,31,33],"a",{"href":32},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002F","Virtual Environments for GIS",". It compares the installation routes, installs QGIS LTR from the official repositories on Debian and Ubuntu, covers Fedora, conda-forge and Flatpak, verifies the bindings, adds Python packages safely, and runs a first headless script.",[14,36,37],{},[38,39,44,48,52,59,68,78,85,91,95,100,104,108,111,114,117,122,126,129,132,135,141,145,148,151],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 280","img","Four Linux installation routes compared: official repositories, distribution packages, conda-forge and Flatpak","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"Ways to install QGIS on Linux",[49,50,51],"desc",{},"Four routes. The official qgis.org repositories for Debian and Ubuntu give current LTR or latest builds integrated with the system Python. Distribution repositories are easy but often a release or more behind. Conda-forge gives an isolated environment with its own Python, good for reproducibility and mixing with scientific packages. Flatpak isolates QGIS from the system, which also isolates its Python, making external scripts harder.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","280","#f6f3ea",[60,61,67],"text",{"x":62,"y":63,"style":64,"fill":65,"textAnchor":66},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Pick by what your scripts need",[53,69],{"x":70,"y":71,"width":72,"height":73,"rx":74,"fill":75,"stroke":76,"style":77},"24","56","168","196","8","#e8efe6","#15803d","stroke-width:2",[60,79,84],{"x":80,"y":81,"style":82,"fill":83,"textAnchor":66},"108","115.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","#166534","qgis.org repos",[60,86,90],{"x":80,"y":87,"style":88,"fill":89,"textAnchor":66},"143.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#2f3b35","current LTR",[60,92,94],{"x":80,"y":93,"style":88,"fill":89,"textAnchor":66},"171.78","system python3",[60,96,99],{"x":80,"y":97,"style":88,"fill":98,"textAnchor":66},"199.78","#59645f","recommended",[53,101],{"x":102,"y":71,"width":72,"height":73,"rx":74,"fill":103,"stroke":98,"style":77},"206","#fffdf7",[60,105,107],{"x":106,"y":81,"style":82,"fill":65,"textAnchor":66},"290","distro repos",[60,109,110],{"x":106,"y":87,"style":88,"fill":89,"textAnchor":66},"easy",[60,112,113],{"x":106,"y":93,"style":88,"fill":89,"textAnchor":66},"often older",[60,115,116],{"x":106,"y":97,"style":88,"fill":98,"textAnchor":66},"check version",[53,118],{"x":119,"y":71,"width":72,"height":73,"rx":74,"fill":120,"stroke":121,"style":77},"388","#eff3ff","#2563eb",[60,123,125],{"x":124,"y":81,"style":82,"fill":121,"textAnchor":66},"472","conda-forge",[60,127,128],{"x":124,"y":87,"style":88,"fill":89,"textAnchor":66},"isolated env",[60,130,131],{"x":124,"y":93,"style":88,"fill":89,"textAnchor":66},"any version",[60,133,134],{"x":124,"y":97,"style":88,"fill":98,"textAnchor":66},"reproducible",[53,136],{"x":137,"y":71,"width":138,"height":73,"rx":74,"fill":139,"stroke":140,"style":77},"570","166","#fdf2e2","#b45309",[60,142,144],{"x":143,"y":81,"style":82,"fill":140,"textAnchor":66},"653","Flatpak",[60,146,147],{"x":143,"y":87,"style":88,"fill":89,"textAnchor":66},"sandboxed",[60,149,150],{"x":143,"y":93,"style":88,"fill":89,"textAnchor":66},"own Python",[60,152,153],{"x":143,"y":97,"style":88,"fill":98,"textAnchor":66},"awkward for scripts",[155,156,158],"h2",{"id":157},"prerequisites","Prerequisites",[160,161,162,166],"ul",{},[163,164,165],"li",{},"A 64-bit Linux distribution: Debian 12 or 13, Ubuntu 22.04 or 24.04, Fedora, or any distribution for the conda route.",[163,167,168],{},"Administrator rights for system packages, or none for conda.",[155,170,172],{"id":171},"choose-ltr-or-latest","Choose LTR or latest",[14,174,175],{},"QGIS has two release lines. The long-term release (LTR) gets bug fixes for a year and is what organisations standardise on; the latest release has new features every four months. For scripts and plugins that others will run, target the LTR — currently the 3.40 series — and test on the latest.",[155,177,179],{"id":178},"install-from-the-official-repositories-debian-and-ubuntu","Install from the official repositories (Debian and Ubuntu)",[14,181,182,183,185],{},"The QGIS project publishes packages for current Debian and Ubuntu releases. Adding its repository gives up-to-date QGIS with Python bindings for the system's ",[18,184,20],{},".",[187,188,193],"pre",{"className":189,"code":190,"language":191,"meta":192,"style":192},"language-bash shiki shiki-themes github-dark","sudo apt install gnupg software-properties-common wget\nsudo mkdir -m755 -p \u002Fetc\u002Fapt\u002Fkeyrings\nsudo wget -O \u002Fetc\u002Fapt\u002Fkeyrings\u002Fqgis-archive-keyring.gpg https:\u002F\u002Fdownload.qgis.org\u002Fdownloads\u002Fqgis-archive-keyring.gpg\n\n# LTR line; use https:\u002F\u002Fqgis.org\u002Fdebian for the latest release instead\ncat \u003C\u003CEOF | sudo tee \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fqgis.sources\nTypes: deb deb-src\nURIs: https:\u002F\u002Fqgis.org\u002Fdebian-ltr\nSuites: $(lsb_release -cs)\nArchitectures: amd64\nComponents: main\nSigned-By: \u002Fetc\u002Fapt\u002Fkeyrings\u002Fqgis-archive-keyring.gpg\nEOF\n\nsudo apt update\nsudo apt install qgis qgis-plugin-grass python3-qgis\n","bash","",[18,194,195,220,238,255,262,269,294,300,306,321,327,333,339,345,350,360],{"__ignoreMap":192},[196,197,200,204,208,211,214,217],"span",{"class":198,"line":199},"line",1,[196,201,203],{"class":202},"svObZ","sudo",[196,205,207],{"class":206},"sU2Wk"," apt",[196,209,210],{"class":206}," install",[196,212,213],{"class":206}," gnupg",[196,215,216],{"class":206}," software-properties-common",[196,218,219],{"class":206}," wget\n",[196,221,223,225,228,232,235],{"class":198,"line":222},2,[196,224,203],{"class":202},[196,226,227],{"class":206}," mkdir",[196,229,231],{"class":230},"sDLfK"," -m755",[196,233,234],{"class":230}," -p",[196,236,237],{"class":206}," \u002Fetc\u002Fapt\u002Fkeyrings\n",[196,239,241,243,246,249,252],{"class":198,"line":240},3,[196,242,203],{"class":202},[196,244,245],{"class":206}," wget",[196,247,248],{"class":230}," -O",[196,250,251],{"class":206}," \u002Fetc\u002Fapt\u002Fkeyrings\u002Fqgis-archive-keyring.gpg",[196,253,254],{"class":206}," https:\u002F\u002Fdownload.qgis.org\u002Fdownloads\u002Fqgis-archive-keyring.gpg\n",[196,256,258],{"class":198,"line":257},4,[196,259,261],{"emptyLinePlaceholder":260},true,"\n",[196,263,265],{"class":198,"line":264},5,[196,266,268],{"class":267},"sjoCn","# LTR line; use https:\u002F\u002Fqgis.org\u002Fdebian for the latest release instead\n",[196,270,272,275,279,282,285,288,291],{"class":198,"line":271},6,[196,273,274],{"class":202},"cat",[196,276,278],{"class":277},"snl16"," \u003C\u003C",[196,280,281],{"class":206},"EOF",[196,283,284],{"class":277}," |",[196,286,287],{"class":202}," sudo",[196,289,290],{"class":206}," tee",[196,292,293],{"class":206}," \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fqgis.sources\n",[196,295,297],{"class":198,"line":296},7,[196,298,299],{"class":206},"Types: deb deb-src\n",[196,301,303],{"class":198,"line":302},8,[196,304,305],{"class":206},"URIs: https:\u002F\u002Fqgis.org\u002Fdebian-ltr\n",[196,307,309,312,315,318],{"class":198,"line":308},9,[196,310,311],{"class":206},"Suites: $(",[196,313,314],{"class":202},"lsb_release",[196,316,317],{"class":230}," -cs",[196,319,320],{"class":206},")\n",[196,322,324],{"class":198,"line":323},10,[196,325,326],{"class":206},"Architectures: amd64\n",[196,328,330],{"class":198,"line":329},11,[196,331,332],{"class":206},"Components: main\n",[196,334,336],{"class":198,"line":335},12,[196,337,338],{"class":206},"Signed-By: \u002Fetc\u002Fapt\u002Fkeyrings\u002Fqgis-archive-keyring.gpg\n",[196,340,342],{"class":198,"line":341},13,[196,343,344],{"class":206},"EOF\n",[196,346,348],{"class":198,"line":347},14,[196,349,261],{"emptyLinePlaceholder":260},[196,351,353,355,357],{"class":198,"line":352},15,[196,354,203],{"class":202},[196,356,207],{"class":206},[196,358,359],{"class":206}," update\n",[196,361,363,365,367,369,372,375],{"class":198,"line":362},16,[196,364,203],{"class":202},[196,366,207],{"class":206},[196,368,210],{"class":206},[196,370,371],{"class":206}," qgis",[196,373,374],{"class":206}," qgis-plugin-grass",[196,376,377],{"class":206}," python3-qgis\n",[14,379,380,384,385,388,389,392,393,396,397,400],{},[381,382,383],"strong",{},"Breakdown:"," The signing key verifies packages; the sources file points apt at the LTR repository for your release's codename. On Ubuntu, use ",[18,386,387],{},"https:\u002F\u002Fqgis.org\u002Fubuntu-ltr"," instead of the Debian URL. ",[18,390,391],{},"python3-qgis"," is the package with the Python bindings — it is pulled in by ",[18,394,395],{},"qgis",", but naming it makes the intent explicit, and on servers you can install it without the desktop application. ",[18,398,399],{},"qgis-plugin-grass"," adds the GRASS Processing provider. Check the current instructions on qgis.org before automating this, as repository paths occasionally change between releases.",[155,402,404],{"id":403},"verify-that-python-sees-qgis","Verify that Python sees QGIS",[14,406,407],{},"The test that matters is whether the system Python can import QGIS and initialise it.",[14,409,410],{},[38,411,414,417,420,423,426,432,438,443,448,451,454,457,460,463,467,470],{"viewBox":412,"role":41,"ariaLabel":413,"xmlns":43},"0 0 760 230","Three verification steps after installing QGIS: import succeeds, version is right, and a headless application initialises with Processing",[45,415,416],{},"Three checks after installing",[49,418,419],{},"First, python3 -c \"import qgis.core\" succeeds. Second, Qgis.version() reports the expected release, such as 3.40. Third, a QgsApplication initialises without a display when QT_QPA_PLATFORM is offscreen and the Processing algorithms load. All three passing means scripts will run.",[53,421],{"x":55,"y":55,"width":56,"height":422,"fill":58},"230",[60,424,425],{"x":62,"y":63,"style":64,"fill":65,"textAnchor":66},"Import, version, headless start",[53,427],{"x":70,"y":71,"width":428,"height":429,"rx":74,"fill":430,"stroke":431,"style":77},"222","150","#eef7f4","#0f766e",[60,433,437],{"x":434,"y":435,"style":436,"fill":431,"textAnchor":66},"135","106.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","import",[60,439,442],{"x":434,"y":440,"style":441,"fill":89,"textAnchor":66},"134.6","text-anchor:middle;font-size:9.5px;font-family:monospace","import qgis.core",[60,444,447],{"x":434,"y":445,"style":446,"fill":98,"textAnchor":66},"162.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","bindings found",[53,449],{"x":450,"y":71,"width":428,"height":429,"rx":74,"fill":120,"stroke":121,"style":77},"269",[60,452,453],{"x":62,"y":435,"style":436,"fill":121,"textAnchor":66},"version",[60,455,456],{"x":62,"y":440,"style":441,"fill":89,"textAnchor":66},"Qgis.version()",[60,458,459],{"x":62,"y":445,"style":446,"fill":98,"textAnchor":66},"expected LTR",[53,461],{"x":462,"y":71,"width":428,"height":429,"rx":74,"fill":75,"stroke":76,"style":77},"514",[60,464,466],{"x":465,"y":435,"style":436,"fill":83,"textAnchor":66},"625","headless",[60,468,469],{"x":465,"y":440,"style":441,"fill":89,"textAnchor":66},"QgsApplication",[60,471,472],{"x":465,"y":445,"style":446,"fill":98,"textAnchor":66},"+ Processing",[187,474,476],{"className":189,"code":475,"language":191,"meta":192,"style":192},"python3 - \u003C\u003C'EOF'\nimport os\nos.environ.setdefault(\"QT_QPA_PLATFORM\", \"offscreen\")\nfrom qgis.core import Qgis, QgsApplication\napp = QgsApplication([], False)\napp.initQgis()\nprint(\"QGIS\", Qgis.version())\nimport processing\nfrom processing.core.Processing import Processing\nProcessing.initialize()\nprint(len(QgsApplication.processingRegistry().algorithms()), \"Processing algorithms\")\napp.exitQgis()\nEOF\n",[18,477,478,490,495,500,505,510,515,520,525,530,535,540,545],{"__ignoreMap":192},[196,479,480,482,485,487],{"class":198,"line":199},[196,481,20],{"class":202},[196,483,484],{"class":206}," -",[196,486,278],{"class":277},[196,488,489],{"class":206},"'EOF'\n",[196,491,492],{"class":198,"line":222},[196,493,494],{"class":206},"import os\n",[196,496,497],{"class":198,"line":240},[196,498,499],{"class":206},"os.environ.setdefault(\"QT_QPA_PLATFORM\", \"offscreen\")\n",[196,501,502],{"class":198,"line":257},[196,503,504],{"class":206},"from qgis.core import Qgis, QgsApplication\n",[196,506,507],{"class":198,"line":264},[196,508,509],{"class":206},"app = QgsApplication([], False)\n",[196,511,512],{"class":198,"line":271},[196,513,514],{"class":206},"app.initQgis()\n",[196,516,517],{"class":198,"line":296},[196,518,519],{"class":206},"print(\"QGIS\", Qgis.version())\n",[196,521,522],{"class":198,"line":302},[196,523,524],{"class":206},"import processing\n",[196,526,527],{"class":198,"line":308},[196,528,529],{"class":206},"from processing.core.Processing import Processing\n",[196,531,532],{"class":198,"line":323},[196,533,534],{"class":206},"Processing.initialize()\n",[196,536,537],{"class":198,"line":329},[196,538,539],{"class":206},"print(len(QgsApplication.processingRegistry().algorithms()), \"Processing algorithms\")\n",[196,541,542],{"class":198,"line":335},[196,543,544],{"class":206},"app.exitQgis()\n",[196,546,547],{"class":198,"line":341},[196,548,344],{"class":206},[14,550,551,553,554,556,557,560,561,564,565,568,569,571,572,574],{},[381,552,383],{}," On a distribution install, ",[18,555,395],{}," is a normal package in the system's ",[18,558,559],{},"dist-packages",", so no ",[18,562,563],{},"PYTHONPATH"," or prefix setup is needed. Setting ",[18,566,567],{},"QT_QPA_PLATFORM=offscreen"," lets the check run over SSH without a display. Initialising Processing and counting algorithms confirms that providers load; a count in the hundreds is normal. If the import fails, the bindings are missing (",[18,570,391],{}," not installed) or a different ",[18,573,20],{}," — a virtual environment, a pyenv shim — is being used.",[155,576,578],{"id":577},"add-python-packages-without-breaking-the-system","Add Python packages without breaking the system",[14,580,581,582,585],{},"Recent Debian and Ubuntu releases mark the system Python as externally managed, so ",[18,583,584],{},"pip install"," into it is refused. Three approaches work.",[187,587,589],{"className":189,"code":588,"language":191,"meta":192,"style":192},"# 1. distribution packages, where available\nsudo apt install python3-pandas python3-geopandas python3-matplotlib\n\n# 2. a virtual environment that can see system packages, including qgis\npython3 -m venv --system-site-packages ~\u002Fvenvs\u002Fpyqgis\nsource ~\u002Fvenvs\u002Fpyqgis\u002Fbin\u002Factivate\npip install scikit-learn rasterio\npython -c \"import qgis.core, sklearn; print('ok')\"\n\n# 3. per-user installs for QGIS Desktop plugins (last resort)\npip install --user --break-system-packages some-package\n",[18,590,591,596,613,617,622,638,646,659,670,674,679],{"__ignoreMap":192},[196,592,593],{"class":198,"line":199},[196,594,595],{"class":267},"# 1. distribution packages, where available\n",[196,597,598,600,602,604,607,610],{"class":198,"line":222},[196,599,203],{"class":202},[196,601,207],{"class":206},[196,603,210],{"class":206},[196,605,606],{"class":206}," python3-pandas",[196,608,609],{"class":206}," python3-geopandas",[196,611,612],{"class":206}," python3-matplotlib\n",[196,614,615],{"class":198,"line":240},[196,616,261],{"emptyLinePlaceholder":260},[196,618,619],{"class":198,"line":257},[196,620,621],{"class":267},"# 2. a virtual environment that can see system packages, including qgis\n",[196,623,624,626,629,632,635],{"class":198,"line":264},[196,625,20],{"class":202},[196,627,628],{"class":230}," -m",[196,630,631],{"class":206}," venv",[196,633,634],{"class":230}," --system-site-packages",[196,636,637],{"class":206}," ~\u002Fvenvs\u002Fpyqgis\n",[196,639,640,643],{"class":198,"line":271},[196,641,642],{"class":230},"source",[196,644,645],{"class":206}," ~\u002Fvenvs\u002Fpyqgis\u002Fbin\u002Factivate\n",[196,647,648,651,653,656],{"class":198,"line":296},[196,649,650],{"class":202},"pip",[196,652,210],{"class":206},[196,654,655],{"class":206}," scikit-learn",[196,657,658],{"class":206}," rasterio\n",[196,660,661,664,667],{"class":198,"line":302},[196,662,663],{"class":202},"python",[196,665,666],{"class":230}," -c",[196,668,669],{"class":206}," \"import qgis.core, sklearn; print('ok')\"\n",[196,671,672],{"class":198,"line":308},[196,673,261],{"emptyLinePlaceholder":260},[196,675,676],{"class":198,"line":323},[196,677,678],{"class":267},"# 3. per-user installs for QGIS Desktop plugins (last resort)\n",[196,680,681,683,685,688,691],{"class":198,"line":329},[196,682,650],{"class":202},[196,684,210],{"class":206},[196,686,687],{"class":230}," --user",[196,689,690],{"class":230}," --break-system-packages",[196,692,693],{"class":206}," some-package\n",[14,695,696,698,699,702,703,707],{},[381,697,383],{}," Distribution packages are the safest, built against the same libraries as QGIS. A virtual environment created with ",[18,700,701],{},"--system-site-packages"," sees QGIS from the system while installing extra packages into its own folder — the best general solution for scripts, as described in ",[30,704,706],{"href":705},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-python-packages-into-qgis\u002F","installing Python packages into QGIS",". Packages with compiled GDAL dependencies, such as rasterio and fiona, must be compatible with the system GDAL; prefer distribution packages for those, or the conda route. Breaking the system-packages guard is a last resort for desktop use.",[155,709,711],{"id":710},"other-routes-fedora-conda-forge-and-flatpak","Other routes: Fedora, conda-forge and Flatpak",[14,713,714],{},"Other distributions and setups have their own sensible routes.",[187,716,718],{"className":189,"code":717,"language":191,"meta":192,"style":192},"# Fedora\nsudo dnf install qgis python3-qgis qgis-grass\n\n# conda-forge: isolated environment with a pinned version\nconda create -n qgis340 -c conda-forge qgis=3.40 python=3.12\nconda activate qgis340\npython -c \"from qgis.core import Qgis; print(Qgis.version())\"\n\n# Flatpak: desktop application, sandboxed\nflatpak install flathub org.qgis.qgis\n",[18,719,720,725,742,746,751,782,792,801,805,810],{"__ignoreMap":192},[196,721,722],{"class":198,"line":199},[196,723,724],{"class":267},"# Fedora\n",[196,726,727,729,732,734,736,739],{"class":198,"line":222},[196,728,203],{"class":202},[196,730,731],{"class":206}," dnf",[196,733,210],{"class":206},[196,735,371],{"class":206},[196,737,738],{"class":206}," python3-qgis",[196,740,741],{"class":206}," qgis-grass\n",[196,743,744],{"class":198,"line":240},[196,745,261],{"emptyLinePlaceholder":260},[196,747,748],{"class":198,"line":257},[196,749,750],{"class":267},"# conda-forge: isolated environment with a pinned version\n",[196,752,753,756,759,762,765,767,770,773,776,779],{"class":198,"line":264},[196,754,755],{"class":202},"conda",[196,757,758],{"class":206}," create",[196,760,761],{"class":230}," -n",[196,763,764],{"class":206}," qgis340",[196,766,666],{"class":230},[196,768,769],{"class":206}," conda-forge",[196,771,772],{"class":206}," qgis=",[196,774,775],{"class":230},"3.40",[196,777,778],{"class":206}," python=",[196,780,781],{"class":230},"3.12\n",[196,783,784,786,789],{"class":198,"line":271},[196,785,755],{"class":202},[196,787,788],{"class":206}," activate",[196,790,791],{"class":206}," qgis340\n",[196,793,794,796,798],{"class":198,"line":296},[196,795,663],{"class":202},[196,797,666],{"class":230},[196,799,800],{"class":206}," \"from qgis.core import Qgis; print(Qgis.version())\"\n",[196,802,803],{"class":198,"line":302},[196,804,261],{"emptyLinePlaceholder":260},[196,806,807],{"class":198,"line":308},[196,808,809],{"class":267},"# Flatpak: desktop application, sandboxed\n",[196,811,812,815,817,820],{"class":198,"line":323},[196,813,814],{"class":202},"flatpak",[196,816,210],{"class":206},[196,818,819],{"class":206}," flathub",[196,821,822],{"class":206}," org.qgis.qgis\n",[14,824,825,827,828,832],{},[381,826,383],{}," Fedora packages QGIS in its own repositories, usually current. Conda-forge provides any QGIS version in an isolated environment with its own Python, GDAL and PROJ — ideal for reproducible analysis, CI and mixing with scientific packages, covered in ",[30,829,831],{"href":830},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba\u002F","using PyQGIS with conda and mamba",". Flatpak runs QGIS Desktop in a sandbox with its own Python inside; scripts run from the system cannot import that QGIS, so Flatpak suits desktop users more than developers.",[155,834,836],{"id":835},"keep-the-installation-current-and-pinned","Keep the installation current and pinned",[14,838,839],{},"An LTR receives point releases with bug fixes roughly monthly. Desktop users want them; servers running scheduled jobs want them only after testing. Apt can do both.",[14,841,842],{},[38,843,846,849,852,854,857,862,867,871,875,878,882,885],{"viewBox":844,"role":41,"ariaLabel":845,"xmlns":43},"0 0 760 196","Desktops receiving each QGIS point release through normal upgrades while servers pin a tested version and move the pin deliberately",[45,847,848],{},"Updates on desktops, pinning on servers",[49,850,851],{},"On desktops, normal apt upgrades bring each LTR point release with bug fixes. On servers, an apt preference pins QGIS packages to a tested version, so routine system upgrades do not change QGIS under running jobs. After testing a new point release, the pin is moved forward deliberately.",[53,853],{"x":55,"y":55,"width":56,"height":73,"fill":58},[60,855,856],{"x":62,"y":63,"style":64,"fill":65,"textAnchor":66},"Desktops follow, servers pin",[53,858],{"x":70,"y":859,"width":860,"height":861,"rx":74,"fill":75,"stroke":76,"style":77},"60","340","120",[60,863,866],{"x":864,"y":865,"style":82,"fill":83,"textAnchor":66},"194","97.78","desktop",[60,868,870],{"x":864,"y":869,"style":88,"fill":89,"textAnchor":66},"123.78","apt upgrade",[60,872,874],{"x":864,"y":873,"style":88,"fill":98,"textAnchor":66},"149.78","every LTR point release",[53,876],{"x":877,"y":859,"width":860,"height":861,"rx":74,"fill":139,"stroke":140,"style":77},"396",[60,879,881],{"x":880,"y":865,"style":82,"fill":140,"textAnchor":66},"566","server",[60,883,884],{"x":880,"y":869,"style":88,"fill":89,"textAnchor":66},"apt pin to 3.40.x",[60,886,887],{"x":880,"y":873,"style":88,"fill":98,"textAnchor":66},"move after testing",[187,889,891],{"className":189,"code":890,"language":191,"meta":192,"style":192},"# server: hold QGIS at a tested version\napt-cache policy python3-qgis | head -5\nsudo tee \u002Fetc\u002Fapt\u002Fpreferences.d\u002Fqgis-pin > \u002Fdev\u002Fnull \u003C\u003C 'PIN'\nPackage: qgis* python3-qgis* libqgis*\nPin: version 1:3.40.11*\nPin-Priority: 1001\nPIN\nsudo apt update && apt-cache policy python3-qgis | head -3\n",[18,892,893,898,916,936,941,946,951,956],{"__ignoreMap":192},[196,894,895],{"class":198,"line":199},[196,896,897],{"class":267},"# server: hold QGIS at a tested version\n",[196,899,900,903,906,908,910,913],{"class":198,"line":222},[196,901,902],{"class":202},"apt-cache",[196,904,905],{"class":206}," policy",[196,907,738],{"class":206},[196,909,284],{"class":277},[196,911,912],{"class":202}," head",[196,914,915],{"class":230}," -5\n",[196,917,918,920,922,925,928,931,933],{"class":198,"line":240},[196,919,203],{"class":202},[196,921,290],{"class":206},[196,923,924],{"class":206}," \u002Fetc\u002Fapt\u002Fpreferences.d\u002Fqgis-pin",[196,926,927],{"class":277}," >",[196,929,930],{"class":206}," \u002Fdev\u002Fnull",[196,932,278],{"class":277},[196,934,935],{"class":206}," 'PIN'\n",[196,937,938],{"class":198,"line":257},[196,939,940],{"class":206},"Package: qgis* python3-qgis* libqgis*\n",[196,942,943],{"class":198,"line":264},[196,944,945],{"class":206},"Pin: version 1:3.40.11*\n",[196,947,948],{"class":198,"line":271},[196,949,950],{"class":206},"Pin-Priority: 1001\n",[196,952,953],{"class":198,"line":296},[196,954,955],{"class":206},"PIN\n",[196,957,958,960,962,965,969,971,973,975,977,979],{"class":198,"line":302},[196,959,203],{"class":202},[196,961,207],{"class":206},[196,963,964],{"class":206}," update",[196,966,968],{"class":967},"s95oV"," && ",[196,970,902],{"class":202},[196,972,905],{"class":206},[196,974,738],{"class":206},[196,976,284],{"class":277},[196,978,912],{"class":202},[196,980,981],{"class":230}," -3\n",[14,983,984,986,987,990,991,185],{},[381,985,383],{}," ",[18,988,989],{},"apt-cache policy"," shows the installed and candidate versions. An apt preference with a priority above 1000 holds every QGIS package at the pinned version — including the matching libraries, so they never get out of step — while the rest of the system keeps updating. The version string here is illustrative; copy the exact one from the policy output. When a new point release has passed your tests, edit the pin and upgrade deliberately. This is the Linux equivalent of the pinned container images in ",[30,992,994],{"href":993},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Frun-pyqgis-in-docker-container\u002F","running PyQGIS in a Docker container",[155,996,998],{"id":997},"run-a-first-headless-script","Run a first headless script",[14,1000,1001],{},"With the bindings verified, a standalone script follows the usual pattern: start the application, do the work, exit cleanly.",[187,1003,1006],{"className":1004,"code":1005,"language":663,"meta":192,"style":192},"language-python shiki shiki-themes github-dark","#!\u002Fusr\u002Fbin\u002Fenv python3\nimport os, sys\nos.environ.setdefault(\"QT_QPA_PLATFORM\", \"offscreen\")\nfrom qgis.core import QgsApplication, QgsVectorLayer\n\napp = QgsApplication([], False)\napp.initQgis()\ntry:\n    layer = QgsVectorLayer(sys.argv[1], \"input\", \"ogr\")\n    if not layer.isValid():\n        raise SystemExit(f\"cannot open {sys.argv[1]}\")\n    print(layer.featureCount(), \"features in\", layer.crs().authid())\nfinally:\n    app.exitQgis()\n",[18,1007,1008,1013,1020,1036,1049,1053,1069,1073,1081,1107,1118,1154,1168,1175],{"__ignoreMap":192},[196,1009,1010],{"class":198,"line":199},[196,1011,1012],{"class":267},"#!\u002Fusr\u002Fbin\u002Fenv python3\n",[196,1014,1015,1017],{"class":198,"line":222},[196,1016,437],{"class":277},[196,1018,1019],{"class":967}," os, sys\n",[196,1021,1022,1025,1028,1031,1034],{"class":198,"line":240},[196,1023,1024],{"class":967},"os.environ.setdefault(",[196,1026,1027],{"class":206},"\"QT_QPA_PLATFORM\"",[196,1029,1030],{"class":967},", ",[196,1032,1033],{"class":206},"\"offscreen\"",[196,1035,320],{"class":967},[196,1037,1038,1041,1044,1046],{"class":198,"line":257},[196,1039,1040],{"class":277},"from",[196,1042,1043],{"class":967}," qgis.core ",[196,1045,437],{"class":277},[196,1047,1048],{"class":967}," QgsApplication, QgsVectorLayer\n",[196,1050,1051],{"class":198,"line":264},[196,1052,261],{"emptyLinePlaceholder":260},[196,1054,1055,1058,1061,1064,1067],{"class":198,"line":271},[196,1056,1057],{"class":967},"app ",[196,1059,1060],{"class":277},"=",[196,1062,1063],{"class":967}," QgsApplication([], ",[196,1065,1066],{"class":230},"False",[196,1068,320],{"class":967},[196,1070,1071],{"class":198,"line":296},[196,1072,514],{"class":967},[196,1074,1075,1078],{"class":198,"line":302},[196,1076,1077],{"class":277},"try",[196,1079,1080],{"class":967},":\n",[196,1082,1083,1086,1088,1091,1094,1097,1100,1102,1105],{"class":198,"line":308},[196,1084,1085],{"class":967},"    layer ",[196,1087,1060],{"class":277},[196,1089,1090],{"class":967}," QgsVectorLayer(sys.argv[",[196,1092,1093],{"class":230},"1",[196,1095,1096],{"class":967},"], ",[196,1098,1099],{"class":206},"\"input\"",[196,1101,1030],{"class":967},[196,1103,1104],{"class":206},"\"ogr\"",[196,1106,320],{"class":967},[196,1108,1109,1112,1115],{"class":198,"line":323},[196,1110,1111],{"class":277},"    if",[196,1113,1114],{"class":277}," not",[196,1116,1117],{"class":967}," layer.isValid():\n",[196,1119,1120,1123,1126,1129,1132,1135,1138,1141,1143,1146,1149,1152],{"class":198,"line":329},[196,1121,1122],{"class":277},"        raise",[196,1124,1125],{"class":230}," SystemExit",[196,1127,1128],{"class":967},"(",[196,1130,1131],{"class":277},"f",[196,1133,1134],{"class":206},"\"cannot open ",[196,1136,1137],{"class":230},"{",[196,1139,1140],{"class":967},"sys.argv[",[196,1142,1093],{"class":230},[196,1144,1145],{"class":967},"]",[196,1147,1148],{"class":230},"}",[196,1150,1151],{"class":206},"\"",[196,1153,320],{"class":967},[196,1155,1156,1159,1162,1165],{"class":198,"line":335},[196,1157,1158],{"class":230},"    print",[196,1160,1161],{"class":967},"(layer.featureCount(), ",[196,1163,1164],{"class":206},"\"features in\"",[196,1166,1167],{"class":967},", layer.crs().authid())\n",[196,1169,1170,1173],{"class":198,"line":341},[196,1171,1172],{"class":277},"finally",[196,1174,1080],{"class":967},[196,1176,1177],{"class":198,"line":347},[196,1178,1179],{"class":967},"    app.exitQgis()\n",[14,1181,1182,1184,1185,1188,1189,1192,1193,1196,1197,1201,1202,185],{},[381,1183,383],{}," The ",[18,1186,1187],{},"try\u002Ffinally"," ensures ",[18,1190,1191],{},"exitQgis"," runs even when the script fails, which avoids crashes at interpreter shutdown. Running it as ",[18,1194,1195],{},"python3 count.py \u002Fdata\u002Froads.gpkg"," needs no environment setup beyond the offscreen platform. For scheduled runs, see ",[30,1198,1200],{"href":1199},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fschedule-pyqgis-scripts-with-cron\u002F","scheduling PyQGIS scripts with cron","; for the general structure of standalone scripts, ",[30,1203,1205],{"href":1204},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","running Python scripts outside QGIS Desktop",[155,1207,1209],{"id":1208},"qgis-version-compatibility","QGIS version compatibility",[14,1211,1212],{},"The official repositories provide QGIS 3.40 LTR and the current latest release for supported Debian and Ubuntu versions; QGIS 4 packages follow the same repository structure as they become available. Python bindings require the Python version the distribution ships; conda-forge allows choosing it. Check qgis.org's installation page for current repository URLs and supported releases.",[155,1214,1216],{"id":1215},"troubleshooting","Troubleshooting",[160,1218,1219,1229,1237,1246],{},[163,1220,1221,986,1226,1228],{},[381,1222,1223,185],{},[18,1224,1225],{},"ModuleNotFoundError: No module named 'qgis'",[18,1227,391],{}," is missing, or a different Python (venv without system packages, pyenv) is active.",[163,1230,1231,1234,1235,185],{},[381,1232,1233],{},"\"externally-managed-environment\" from pip."," Use distribution packages or a venv with ",[18,1236,701],{},[163,1238,1239,1242,1243,1245],{},[381,1240,1241],{},"\"could not connect to display\"."," Set ",[18,1244,567],{}," for headless runs.",[163,1247,1248,1251,1252,185],{},[381,1249,1250],{},"Old QGIS version after install."," The distribution repository was used instead of qgis.org; check ",[18,1253,1254],{},"apt policy qgis",[155,1256,1258],{"id":1257},"conclusion","Conclusion",[14,1260,1261,1262,1264,1265,1267],{},"Install QGIS LTR from the official qgis.org repositories on Debian and Ubuntu (or Fedora's own packages), verify that ",[18,1263,20],{}," imports ",[18,1266,24],{},", reports the right version and starts headless with Processing, add Python packages through distribution packages or a venv with system site packages, use conda-forge for isolated, pinned environments, and avoid Flatpak for scripting.",[155,1269,1271],{"id":1270},"frequently-asked-questions","Frequently Asked Questions",[14,1273,1274,1277],{},[381,1275,1276],{},"Can I have LTR and latest installed together?","\nNot from the system repositories; use conda environments for side-by-side versions.",[14,1279,1280,1283,1284,1286],{},[381,1281,1282],{},"Which Python version does QGIS use on Linux?","\nThe distribution's default ",[18,1285,20],{}," — 3.11 on Debian 12, 3.12 on Ubuntu 24.04. Scripts must run with that interpreter, or with a venv built from it.",[14,1288,1289,1292,1293,1295],{},[381,1290,1291],{},"Do I need the desktop application on a server?","\nNo — ",[18,1294,391],{}," and the server or provider packages suffice for headless scripts.",[14,1297,1298,1301,1302,1305,1306,185],{},[381,1299,1300],{},"Why does my IDE not find qgis?","\nIt uses a different interpreter; point it at ",[18,1303,1304],{},"\u002Fusr\u002Fbin\u002Fpython3"," or the venv created with system site packages, as in ",[30,1307,1309],{"href":1308},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fconfigure-vs-code-for-pyqgis-development\u002F","configuring VS Code for PyQGIS",[14,1311,1312,1315,1316,1319,1320,1323],{},[381,1313,1314],{},"How do I remove a broken installation cleanly?","\nPurge the packages with ",[18,1317,1318],{},"sudo apt purge 'qgis*' 'python3-qgis*' 'libqgis*'",", remove the repository file, then reinstall. User profiles in ",[18,1321,1322],{},"~\u002F.local\u002Fshare\u002FQGIS"," are untouched, so settings and plugins survive.",[14,1325,1326,1329],{},[381,1327,1328],{},"Are nightly builds available?","\nYes, from qgis.org's nightly repositories, for testing upcoming changes.",[155,1331,1333],{"id":1332},"related","Related",[160,1335,1336,1341,1347,1352,1357],{},[163,1337,1338,1340],{},[30,1339,33],{"href":32}," — the guide this recipe belongs to",[163,1342,1343],{},[30,1344,1346],{"href":1345},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fhow-to-install-qgis-python-bindings-on-windows\u002F","How to Install QGIS Python Bindings on Windows",[163,1348,1349],{},[30,1350,1351],{"href":830},"Use PyQGIS with Conda and Mamba",[163,1353,1354],{},[30,1355,1356],{"href":705},"Install Python Packages into QGIS",[163,1358,1359],{},[30,1360,1361],{"href":1204},"Running Python Scripts Outside QGIS Desktop",[1363,1364,1365],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#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 pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}",{"title":192,"searchDepth":222,"depth":222,"links":1367},[1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380],{"id":157,"depth":222,"text":158},{"id":171,"depth":222,"text":172},{"id":178,"depth":222,"text":179},{"id":403,"depth":222,"text":404},{"id":577,"depth":222,"text":578},{"id":710,"depth":222,"text":711},{"id":835,"depth":222,"text":836},{"id":997,"depth":222,"text":998},{"id":1208,"depth":222,"text":1209},{"id":1215,"depth":222,"text":1216},{"id":1257,"depth":222,"text":1258},{"id":1270,"depth":222,"text":1271},{"id":1332,"depth":222,"text":1333},"Get a working PyQGIS on Linux — the official QGIS repositories for Debian and Ubuntu, Fedora packages, conda-forge and Flatpak, choosing LTR or latest, verifying that python3 can import qgis.core, adding packages without breaking the system, and running scripts headless.","md",{"slug":12,"type":1384,"breadcrumb":5,"datePublished":1385,"dateModified":1385},"article","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-qgis-with-python-bindings-on-linux",{"title":5,"description":1381},"pyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-qgis-with-python-bindings-on-linux\u002Findex","Jyu3uuJTUfs7ND6C73fR0jf_ALCXEsBGfClUrVmViSY",1790966256067]