Share your QGIS projects with QGIS Server
What is this post about?
Last year we at Gispo got to work with the National Land Survey of Finland on a project focused on QGIS Server. We learned a lot during said project, and this blog post aims to share some of that as a practical introduction to QGIS Server. Topics include: what is QGIS Server, why should you care about it, and how to publish maps with it. To provide an example, we set up QGIS Server to publish a WMS as well as a map atlas of print-ready maps. A small example project is included as a hands-on example.
Why consider QGIS Server?
QGIS Server publishes maps and geospatial data to the web via the standard OGC web services (WMS, WFS, OGC API for Features etc). While this alone is not particularly exciting, QGIS Server does have a couple of unique characteristics that differentiate it from any other server for geospatial data. The gist is that, as the name suggests, QGIS Server is highly integrated with QGIS. For example, you can configure and publish your web services within QGIS, directly from your QGIS projects. What is really cool – perhaps the single most important feature of QGIS Server – is that the integration with QGIS extends to styling the maps as well: Once published, your maps look identical to what they look like in your desktop QGIS.

Another unique characteristic of QGIS Server is its capability to serve QGIS print layouts. So, in addition to styling your layers with QGIS, you can use all the print-layout-specific features of QGIS as well: legends, map frames, or even publish a map atlas on the web.

Getting started with QGIS Server
There are many ways to get started with QGIS Server and, of course, what way is best depends heavily on context. In this post we go for a containerized approach. This is convenient as it means we can get a working server configuration up and running very quickly and without a ton of installations on our own system – a container engine, such as Podman or Docker, is all that is needed to get started (for a detailed description of the alternatives in setting up QGIS Server, the official documentation does a great job).
Let’s grab the QGIS Server image. Note to edit the version if needed: Since the project configuration happens through QGIS Desktop, it is best to match the versions of QGIS Desktop / Server when preparing projects.
docker pull qgis/qgis-server:3.40
Also, while we use the official image here, it is worth noting that it is by no means the only container implementation – there are numerous different container images of QGIS server, all with varying software stacks and configurations.
Now, running the server becomes as as simple as:
docker run --rm qgis/qgis-server:3.40
This obviously does not do much of anything yet: We have not given QGIS Server any QGIS projects to serve, nor have we exposed any ports to actually communicate with the server container.
Serving a project
Get your favourite QGIS project ready (any QGIS project will do). An example project is also included in the example repository.
As mentioned before, configuring a QGIS project to be served happens from your desktop QGIS. In the Server tab of your Project Properties you have the options that directly relate to configuring your services: from the general services capabilities that generate your GetCapabilities document to service-specific settings such as your WMS extent or published / excluded layers.

Now that we have a project ready to go, QGIS Server has to be able to access it (and its data). Since we are dealing with containers, we’ll need to mount a volume containing our QGIS project and its data into the container. We’ll also need a way for us to communicate with the server container, so a port on our host machine should be mapped to the container port where QGIS Server is listening.
As we start specifying more conditions for our container, it is best to move to using a compose.yml file for defining how we want our container to run. So, the above translated to a compose file:
services:
qgis-server:
image: qgis/qgis-server:3.40
container_name: example-qgis-server
ports:
- 8080:80
volumes:
- ./data:/io/data
Now we can start the server with docker compose up -d. Our local project directory (./data) is mapped to /io/data in the container, where QGIS Server is configured to look for projects to serve. The default port of the server container is mapped to local port 8080 meaning we can access the server on localhost:8080.
Let’s try the WMS. The container implementation we are using expects our request urls to be prefixed with /ogc/<project-name> for QGIS Server to find the project. The example project is named example_project.qgs, so a valid request could be, for example:
localhost:8080/ogc/example_project?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities
Publishing & configuring layers
By default, the layers in your QGIS project are the layers that are published by QGIS Server. How you style the layers in QGIS is, cartographically speaking, the layer configuration (the Server settings in project properties have the service-specific options). One other helpful thing when it comes to layer publishing is layer grouping. By grouping you can publish single layers that are made of many other layers – requests can just pass the layer group’s name to the LAYERS parameter of GetMap.

http://localhost:8080/ogc/example_project?SERVICE=WMS
&VERSION=1.3.0
&REQUEST=GetMap
&LAYERS=wms_group
&CRS=EPSG:4326
&WIDTH=1000
&HEIGHT=750
&BBOX=28,119,43,139
The example project has a layer group with three layers. We can request for all of them by specifying the group in the request.
On the topic of layer configuration it is also worth noting that QGIS can do a lot more than apply static styles. This has immense potential as we can define many aspects of the published maps to be dynamic. For example, in the example project the visibility of the populated places is controlled with a rule that shows different cities based on zoom level. This translates directly to how the layer works through the WMS: We see different cities depending on our zoom level just like in desktop QGIS.



Layer symbology settings and two WMS responses at different scales. The published layers fully utilize QGIS for styling – for example when defining rule-based symbology.
Serving a map atlas
QGIS Server extends the WMS-specification with a GetPrint request. With GetPrint we can request print layouts from QGIS projects by specifying the desired print layout with the TEMPLATE parameter, and the response will be the entire layout instead of a single map image. Since a print layout can have multiple maps, the parameters of individual maps are controlled by prefixing them with the id of the map item, for example map0:EXTENT. All the GetPrint specifics are documented in more detail in the docs.
Where the capability to serve print layouts really shines is when it is combined with the atlas functionalities of the print layout. We can define a map atlas in QGIS, and then request parts of it from QGIS Server by using a GetPrint request together with the ATLAS_PK parameter. This parameter works with the coverage layer of the atlas, allowing us to specify which parts of the atlas we want to request based on the atlas page name.
For example, in the example project we have a print layout named country_atlas. It has a map atlas that uses the country layer as the atlas coverage, and the layer’s “fid” column as the atlas page name. Thus, a request for atlas pages 1 and 2 (so countries with fid 1 and 2) would look like:
http://localhost:8080/ogc/example_project?SERVICE=WMS
&VERSION=1.3.0
&REQUEST=GetPrint
&FORMAT=pdf
&TEMPLATE=country_atlas
&CRS=EPSG:4326
&ATLAS_PK=1,2
And our response:

One final QGIS feature that has to be mentioned especially in the context of serving atlas maps is the ability to specify data defined overrides for nearly any parameter in the print layout. This means that we can manipulate the atlas pages with, for example, expressions or attribute values. In the context of atlas maps, overriding parameters using attribute values enables us to use the attributes of the coverage layer as values for the parameters. In the example country_atlas layout, we control the main map’s projection with the country layer’s “epsg” attribute. This attribute simply holds an epsg code for every country, and depending which country we request, the projection changes based on this code automatically. So, no matter what CRS we specify in the GetPrint request, we always get a map projected in a predetermined CRS for the specified country.

What’s next?
While this post already touched on some quite novel ways to craft map services, it really is just scratching the surface of what is possible with the tools presented. And, of course, QGIS Server integrates with tools other than QGIS as well: for example, we could use a PostGIS backend to store our data (or even QGIS projects) that we intend to draw into maps and serve with QGIS Server. Also, just like QGIS, QGIS Server supports plugins written in Python, meaning its capabilities can be extended even further relatively easily.
All in all, integrating QGIS with the capabilities of a map server enables a high degree of customizability and allows for functionalities that could benefit quite a few use cases. If you think yours is one of them, do reach out!