Setting up nginx and geoserver in a reverse proxy configuration in Docker

A free screencast (video) course is available for this post but you need to be signed in order to view it, you can sign in here if you already have an account or register here if you don't have one.

Our Geoserver setup is now working locally, we use OpenLayers to connect to Geoserver on http://localhost:8081/geoserver, and it is working correctly. However, this setup is not optimal for two main reasons:

  • When we deploy to production, we will have two applications with two different ports. What will we do? Use two domains (i.e., and or subdomains (i.e., and It would be a possible solution, but with WFS, we will run into CORS issues. CORS or Cross-Origin Resource Sharing is a protection mechanism used by browsers and web servers to prevent javascript from loading resources from different domains that aren't the script's origin. It is precisely what OpenLayers (javascript), originating from the domain does; it's making a query on the WFS service to load a json resource on a different origin: There are two possible solutions to this problem; the first is to configure the server to explicitly allow requests from specific origins (Geoserver supports this setting). The second is to have everything within the same domain (origin). It is the one we will achieve with Nginx reverse proxy.
  • The second reason is that our setup is not safe at this moment. Geoserver is a Java application deployed using the Apache Tomcat framework (in our setup). The Tomcat application has an administrative control panel that we do not want to be publicly accessible. At this time, it is available at http://localhost:8081:

Use geoserver through nginx reverse proxy Let's use our existing proxy service to reverse proxy everything coming on http://localhost:8080/geoserver to our Geoserver service (named "geoserver" in the docker-compose.yml file) on the same directory. To do so, we will need first to run "docker-compose down" to make sure our containers are not running, then make the following changes to docker/nginx/nginx-site.conf:

server {
    listen 80;
    root /var/www/app/public;
    index index.php;
    server_name _;

    location / {
         try_files $uri $uri/ /index.php$is_args$args;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    location ^~ /geoserver/ {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto http;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://geoserver:8080/geoserver/;

In the docker-compose.yml file, we can now close the 8081 port opened in the Geoserver service; this service will only be accessible through the Nginx proxy on url http://localhost:8080/geoserver:

        image: kartoza/geoserver:latest
            - "8081:8080"
            - geoserver-data:/opt/geoserver/data_dir
            GEOSERVER_DATA_DIR: /opt/geoserver/data_dir
            GEOSERVER_ADMIN_USER: admin
            GEOSERVER_ADMIN_PASSWORD: geoserver
            - backend

Now, run "docker-compose down" and "docker-compose up" again; the http://localhost:8081 Tomcat admin page should not be available anymore. We can directly connect to Geoserver through http://localhost:8080/geoserver. The final step is to tell Geoserver about our reverse proxy, so it knows how to generate proper urls. In the Geoserver administrative page, click on the "Global" link in the "Settings" section of the menu on the left and fill the "Proxy Base URL" with "http://localhost:8080/geoserver" then click save:

Use geoserver through nginx reverse proxy Our proxy is now functional, let's try it in OpenLayers, in the resources/js/components/map.js, change the base WFS url like this:

                const paramsObj = {
                    servive: 'WFS',
                    version: '2.0.0',
                    request: 'GetFeature',
                    typeName: 'laravelgis:monuments',
                    outputFormat: 'application/json',
                    crs: 'EPSG:4326',
                    srsName: 'EPSG:4326',

                const urlParams = new URLSearchParams(paramsObj)
                const monumentsUrl = 'http://localhost:8081/geoserver/wfs?' +  urlParams.toString()
                const monumentsUrl = 'http://localhost:8080/geoserver/wfs?' +  urlParams.toString()

                this.monumentsLayer = new VectorLayer({
                    source: new VectorSource({
                        format: new GeoJSON(),
                        url: monumentsUrl,
                    style: this.styleFunction,
                    label: "Monuments",

Let's run our watcher ("dr npm run dev") and go to http://localhost:8080/dashboard, we should still see our monuments on the map, but they are now loaded from the same origin as our application (http://localhost:8080) through the Nginx reverse proxy:

Use geoserver through nginx reverse proxy We are now using a much better approach for using WFS on Geoserver. When we deploy to production, we can configure our Nginx with https with only one domain and one certificate for our Laravel application AND our Geoserver application. We now have a single point of entry to our web application.

Our backend is looking pretty good at this moment; in the next post, we will go back to the front end a little bit by refactoring our legend and adding a new interaction to the map: a popup that shows the name and picture when the user clicks on a monument. The popup is an overlay that will appear over the map at the location clicked by the user.

The commit for this post is available here: use-geoserver-through-nginx-reverse-proxy

First published 1 year ago
Latest update 10 months ago
wim debbaut
Posted by wim debbaut 10 months ago

Great explanation along the way my dear friend - written and by audio/video. My dev backend with reverse proxying works as a charm uptil now. I look forward to implement this three tier docker architecture on our GIS production environment soon - using $ArcGis$ now. But first off, I will just continue with the rest of the chapters (10 -16) in this dev environment. Keep on the good work.

Posted by webgisdev 10 months ago

Thank you for your kind comments, it's a great motivation to continue the project!

Posted by Steve 4 months ago

Thank you for this excellent course - what a fantastic resource you have put together, chapeau.

I am have followed along up to this point, but I am using Laravel Sail implementation rather than native Docker. I managed to get GeoServer up and running with no problems, but less clear on how to configure the reverse proxy as I never had to set up docker/nginx/nginx-site.conf: that as you did at the start.

I can remove geoserver port in docker-compose.yml and update the monumentsUrl map.js, but not sure where to add the reverse proxy settings.

location ^~ /geoserver/ {

    proxy_set_header X-Real-IP $remote_addr;

    proxy_set_header Host $http_host;

    proxy_set_header X-Forwarded-Proto http;

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_pass http://geoserver:8080/geoserver/;

Any idea how to get this working using Sail.

Thanks again...looking forward to working through everything else you have created.

Posted by webgisdev 4 months ago

Hello Steve,

I don't have much experience with Laravel Sail but I know it doesn't use nginx by default.

It's probably possible to tweak it to use nginx (there are some tutorials on the topic out there you can possibly look at).

Thanks for your kind comment!

You need to be signed in to post comments, you can sign in here if you already have an account or register here if you don't.