GeoNode Vanilla


The following steps will guide you to a fresh setup of GeoNode.

All guides will first install and configure the system to run it in DEBUG mode (also known as DEVELOPMENT mode) and then by configuring an HTTPD server to serve GeoNode through the standard HTTP (80) port.


Those guides are not meant to be used on a production system. There will be dedicated chapters that will show you some hints to optimize GeoNode for a production-ready machine. In any case, we strongly suggest to task an experienced DevOp or System Administrator before exposing your server to the WEB.

Ubuntu 22.04 LTS

This part of the documentation describes the complete setup process for GeoNode on an Ubuntu 22.04.1LTS 64-bit clean environment (Desktop or Server).

All examples use shell commands that you must enter on a local terminal or a remote shell.

  • If you have a graphical desktop environment you can open the terminal application after login;

  • if you are working on a remote server the provider or sysadmin should has given you access through an ssh client.

1. Install the dependencies

In this section, we are going to install all the basic packages and tools needed for a complete GeoNode installation.


To follow this guide, a basic knowledge about Ubuntu Server configuration and working with a shell is required.


This guide uses vim as the editor; fill free to use nano, gedit or others.

Upgrade system packages

Check that your system is already up-to-date with the repository running the following commands:

sudo add-apt-repository ppa:ubuntugis/ppa
sudo apt update -y

Packages Installation


You don’t need to install the system packages if you want to run the project using Docker

We will use as fictitious Domain Name.

First, we are going to install all the system packages needed for the GeoNode setup. Login to the target machine and execute the following commands:

# Install packages from GeoNode core
sudo apt install -y --allow-downgrades build-essential \
  python3-gdal=3.4.1+dfsg-1build4 gdal-bin=3.4.1+dfsg-1build4 libgdal-dev=3.4.1+dfsg-1build4 \
  python3-all-dev python3.10-dev python3.10-venv virtualenvwrapper \
  libxml2 libxml2-dev gettext libmemcached-dev zlib1g-dev \
  libxslt1-dev libjpeg-dev libpng-dev libpq-dev \
  software-properties-common build-essential \
  git unzip gcc zlib1g-dev libgeos-dev libproj-dev \
  sqlite3 spatialite-bin libsqlite3-mod-spatialite libsqlite3-dev

# Install Openjdk
sudo apt install openjdk-11-jdk-headless default-jdk-headless -y

# Verify GDAL version
gdalinfo --version
  $> GDAL 3.4.1, released 2021/12/27

# Verify Python version
python3.10 --version
  $> Python 3.10.4

which python3.10
  $> /usr/bin/python3.10

# Verify Java version
java -version
  $> openjdk version "11.0.16"

# Install VIM
sudo apt install -y vim

# Cleanup the packages
sudo apt update -y; sudo apt autoremove --purge


GeoNode 4.1.x is not compatible with Python < 3.7

2. GeoNode Installation

This is the most basic installation of GeoNode. It won’t use any external server like Apache Tomcat, PostgreSQL or HTTPD.

First of all we need to prepare a new Python Virtual Environment

Since geonode needs a large number of different python libraries and packages, its recommended to use a python virtual environment to avoid conflicts on dependencies with system wide python packages and other installed software. See also documentation of Virtualenvwrapper package for more information


The GeoNode Virtual Environment must be created only the first time. You won’t need to create it again everytime.

which python3.10  # copy the path of python executable

# Create the GeoNode Virtual Environment (first time only)
export WORKON_HOME=~/.virtualenvs
source /usr/share/virtualenvwrapper/
mkvirtualenv --python=/usr/bin/python3.10 geonode  # Use the python path from above

# Alterantively you can also create the virtual env like below
mkdir -p ~/.virtualenvs
python3.10 -m venv ~/.virtualenvs/geonode
source ~/.virtualenvs/geonode/bin/activate

At this point your command prompt shows a (geonode) prefix, this indicates that your virtualenv is active.


The next time you need to access the Virtual Environment just run

source /usr/share/virtualenvwrapper/
workon geonode

# Alterantively you can also create the virtual env like below
source ~/.virtualenvs/geonode/bin/activate


In order to save permanently the virtualenvwrapper environment

vim ~/.bashrc

# Write to the bottom of the file the following lines
export WORKON_HOME=~/.virtualenvs
source /usr/share/virtualenvwrapper/
# Let's create the GeoNode core base folder and clone it
sudo mkdir -p /opt/geonode/; sudo usermod -a -G www-data $USER; sudo chown -Rf $USER:www-data /opt/geonode/; sudo chmod -Rf 775 /opt/geonode/

# Clone the GeoNode source code on /opt/geonode
cd /opt; git clone -b 4.1.x geonode
# Install the Python packages
cd /opt/geonode
pip install -r requirements.txt --upgrade
pip install -e . --upgrade
pip install pygdal=="`gdal-config --version`.*"

3. Postgis database Setup

Be sure you have successfully completed all the steps of the section 1. Install the dependencies.

In this section, we are going to setup users and databases for GeoNode in PostgreSQL.

Install and Configure the PostgreSQL Database System

In this section we are going to install the PostgreSQL packages along with the PostGIS extension. Those steps must be done only if you don’t have the DB already installed on your system.

# Ubuntu 22.04.1 (focal)
sudo sh -c 'echo "deb `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
sudo wget --no-check-certificate --quiet -O - | sudo apt-key add -
sudo apt update -y; sudo apt install -y postgresql-13 postgresql-13-postgis-3 postgresql-13-postgis-3-scripts postgresql-13 postgresql-client-13

We now must create two databases, geonode and geonode_data, belonging to the role geonode.


This is our default configuration. You can use any database or role you need. The connection parameters must be correctly configured on settings, as we will see later in this section.

Databases and Permissions

First, create the geonode user. GeoNode is going to use this user to access the database

sudo service postgresql start
sudo -u postgres createuser -P geonode

# Use the password: geonode

You will be prompted asked to set a password for the user. Enter geonode as password.


This is a sample password used for the sake of simplicity. This password is very weak and should be changed in a production environment.

Create database geonode and geonode_data with owner geonode

sudo -u postgres createdb -O geonode geonode
sudo -u postgres createdb -O geonode geonode_data

Next let’s create PostGIS extensions

sudo -u postgres psql -d geonode -c 'CREATE EXTENSION postgis;'
sudo -u postgres psql -d geonode -c 'GRANT ALL ON geometry_columns TO PUBLIC;'
sudo -u postgres psql -d geonode -c 'GRANT ALL ON spatial_ref_sys TO PUBLIC;'
sudo -u postgres psql -d geonode -c 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO geonode;'
sudo -u postgres psql -d geonode -c 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO geonode;'

sudo -u postgres psql -d geonode_data -c 'CREATE EXTENSION postgis;'
sudo -u postgres psql -d geonode_data -c 'GRANT ALL ON geometry_columns TO PUBLIC;'
sudo -u postgres psql -d geonode_data -c 'GRANT ALL ON spatial_ref_sys TO PUBLIC;'
sudo -u postgres psql -d geonode_data -c 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO geonode;'
sudo -u postgres psql -d geonode_data -c 'GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO geonode;'

Final step is to change user access policies for local connections in the file pg_hba.conf

sudo vim /etc/postgresql/13/main/pg_hba.conf

Scroll down to the bottom of the document. We want to make local connection trusted for the default user.

Make sure your configuration looks like the one below.

# If you change this first entry you will need to make sure that the
# database superuser can access the database using some other method.
# Noninteractive access to all databases is required during automatic
# maintenance (custom daily cronjobs, replication, and similar tasks).
# Database administrative login by Unix domain socket
local   all             postgres                                trust

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     md5
# IPv4 local connections:
host    all             all               md5
# IPv6 local connections:
host    all             all             ::1/128                 md5
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     peer
host    replication     all               md5
host    replication     all             ::1/128                 md5


If your PostgreSQL database resides on a separate/remote machine, you’ll have to allow remote access to the databases in the /etc/postgresql/13/main/pg_hba.conf to the geonode user and tell PostgreSQL to accept non-local connections in your /etc/postgresql/13/main/postgresql.conf file

Restart PostgreSQL to make the change effective.

sudo service postgresql restart

PostgreSQL is now ready. To test the configuration, try to connect to the geonode database as geonode role.

psql -U postgres geonode
# This should not ask for any password

psql -U geonode geonode
# This should ask for the password geonode

# Repeat the test with geonode_data DB
psql -U postgres geonode_data
psql -U geonode geonode_data

4. Install GeoServer

In this section, we are going to install the Apache Tomcat 8 Servlet Java container, which will be started by default on the internal port 8080.

We will also perform several optimizations to:

  1. Correctly setup the Java VM Options, like the available heap memory and the garbage collector options.

  2. Externalize the GeoServer and GeoWebcache catalogs in order to allow further updates without the risk of deleting our datasets.


This is still a basic setup of those components. More details will be provided on sections of the documentation concerning the hardening of the system in a production environment. Nevertheless, you will need to tweak a bit those settings accordingly with your current system. As an instance, if your machine does not have enough memory, you will need to lower down the initial amount of available heap memory. Warnings and notes will be placed below the statements that will require your attention.

Install Apache Tomcat 9 (ref.


Apache Tomcat 9 requires Java 8 or newer to be installed on the server. Check the steps before in order to be sure you have OpenJDK 8 correctly installed on your system.

First, it is not recommended to run Apache Tomcat as user root, so we will create a new system user which will run the Apache Tomcat server

sudo useradd -m -U -d /opt/tomcat -s /bin/bash tomcat
sudo usermod -a -G www-data tomcat


Now, go to the official Apache Tomcat website and download the most recent version of the software to your server. But don’t use Tomcat10 because there are still some errors between Geoserver and Tomcat.

VERSION=9.0.65; wget${VERSION}/bin/apache-tomcat-${VERSION}.tar.gz

Once the download is complete, extract the tar file to the /opt/tomcat directory:

sudo mkdir /opt/tomcat
sudo tar -xf apache-tomcat-${VERSION}.tar.gz -C /opt/tomcat/; rm apache-tomcat-${VERSION}.tar.gz

Apache Tomcat is updated regulary. So, to have more control over versions and updates, we’ll create a symbolic link as below:

sudo ln -s /opt/tomcat/apache-tomcat-${VERSION} /opt/tomcat/latest

Now, let’s change the ownership of all Apache Tomcat files as below:

sudo chown -R tomcat:www-data /opt/tomcat/

Make the shell scripts inside the bin directory executable:

sudo sh -c 'chmod +x /opt/tomcat/latest/bin/*.sh'

Create the a systemd file with the following content:

# Check the correct JAVA_HOME location
JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::")
  $> /usr/lib/jvm/java-1.11.0-openjdk-amd64/jre/

# Let's create a symbolic link to the JRE
sudo ln -s /usr/lib/jvm/java-1.11.0-openjdk-amd64/jre/ /usr/lib/jvm/jre

# Let's create the tomcat service
sudo vim /etc/systemd/system/tomcat9.service
Description=Tomcat 9 servlet container



Environment=" -Djava.awt.headless=true"

Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC"



Now you can start the Apache Tomcat 9 server and enable it to start on boot time using the following command:

sudo systemctl daemon-reload
sudo systemctl start tomcat9.service
sudo systemctl status tomcat9.service
sudo systemctl enable tomcat9.service

For verification, type the following ss command, which will show you the 8080 open port number, the default open port reserved for Apache Tomcat Server.

ss -ltn

In a clean Ubuntu 22.04.1, the ss command may not be found and the iproute2 library should be installed first.

sudo apt install iproute2
# Then run the ss command
ss -ltn

In a clean Ubuntu 22.04.1, the ss command may not be found and the iproute2 library should be installed first.

sudo apt install iproute2
# Then run the ss command
ss -ltn

If your server is protected by a firewall and you want to access Tomcat from the outside of your local network, you need to open port 8080.

Use the following command to open the necessary port:

sudo ufw allow 8080/tcp


Generally, when running Tomcat in a production environment, you should use a load balancer or reverse proxy.

It’s a best practice to allow access to port 8080 only from your internal network.

We will use NGINX in order to provide Apache Tomcat through the standard HTTP port.


Alternatively you can define the Tomcat Service as follow, in case you would like to use systemctl

sudo vim /usr/lib/systemd/system/tomcat9.service
Description=Apache Tomcat Server





sudo systemctl daemon-reload
sudo systemctl enable tomcat9.service
sudo systemctl start tomcat9.service

Install GeoServer on Tomcat9

Let’s externalize the GEOSERVER_DATA_DIR and logs

# Create the target folders
sudo mkdir -p /opt/data
sudo chown -Rf $USER:www-data /opt/data
sudo chmod -Rf 775 /opt/data
sudo mkdir -p /opt/data/logs
sudo chown -Rf $USER:www-data /opt/data/logs
sudo chmod -Rf 775 /opt/data/logs

# Download and extract the default GEOSERVER_DATA_DIR
sudo wget --no-check-certificate "$GS_VERSION/" -O data-$

sudo unzip data-$ -d /opt/data/

sudo mv /opt/data/data/ /opt/data/geoserver_data
sudo chown -Rf tomcat:www-data /opt/data/geoserver_data
sudo chmod -Rf 775 /opt/data/geoserver_data

sudo mkdir -p /opt/data/geoserver_logs
sudo chown -Rf tomcat:www-data /opt/data/geoserver_logs
sudo chmod -Rf 775 /opt/data/geoserver_logs

sudo mkdir -p /opt/data/gwc_cache_dir
sudo chown -Rf tomcat:www-data /opt/data/gwc_cache_dir
sudo chmod -Rf 775 /opt/data/gwc_cache_dir

# Download and install GeoServer
sudo wget --no-check-certificate "$GS_VERSION/geoserver.war" -O geoserver-$GS_VERSION.war
sudo mv geoserver-$GS_VERSION.war /opt/tomcat/latest/webapps/geoserver.war

Let’s now configure the JAVA_OPTS, i.e. the parameters to run the Servlet Container, like heap memory, garbage collector and so on.

sudo sed -i -e 's/xom-\*\.jar/xom-\*\.jar,bcprov\*\.jar/g' /opt/tomcat/latest/conf/

export JAVA_HOME=$(readlink -f /usr/bin/java | sed "s:bin/java::")
echo 'JAVA_HOME='$JAVA_HOME | sudo tee --append /opt/tomcat/latest/bin/
sudo sed -i -e "s/JAVA_OPTS=/#JAVA_OPTS=/g" /opt/tomcat/latest/bin/

echo 'GEOSERVER_DATA_DIR="/opt/data/geoserver_data"' | sudo tee --append /opt/tomcat/latest/bin/
echo 'GEOSERVER_LOG_LOCATION="/opt/data/geoserver_logs/geoserver.log"' | sudo tee --append /opt/tomcat/latest/bin/
echo 'GEOWEBCACHE_CACHE_DIR="/opt/data/gwc_cache_dir"' | sudo tee --append /opt/tomcat/latest/bin/
echo 'GEOFENCE_DIR="$GEOSERVER_DATA_DIR/geofence"' | sudo tee --append /opt/tomcat/latest/bin/
echo 'TIMEZONE="UTC"' | sudo tee --append /opt/tomcat/latest/bin/

echo 'JAVA_OPTS="-server -Djava.awt.headless=true -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -XX:+UseParallelGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Duser.timezone=$TIMEZONE -Xms512m -Xmx4096m -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DGEOSERVER_DATA_DIR=$GEOSERVER_DATA_DIR -Dgeofence.dir=$GEOFENCE_DIR -DGEOSERVER_LOG_LOCATION=$GEOSERVER_LOG_LOCATION -DGEOWEBCACHE_CACHE_DIR=$GEOWEBCACHE_CACHE_DIR -Dgwc.context.suffix=gwc"' | sudo tee --append /opt/tomcat/latest/bin/


After the execution of the above statements, you should be able to see the new options written at the bottom of the file /opt/tomcat/latest/bin/

# If you run Tomcat on port numbers that are all higher than 1023, then you
# do not need authbind.  It is used for binding Tomcat to lower port numbers.
# (yes/no, default: no)
JAVA_OPTS="-server -Djava.awt.headless=true -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -XX:+UseParallelGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Duser.timezone=$TIMEZONE -Xms512m -Xmx4096m -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://localhost:8080/geoserver/pdf -DGEOSERVER_DATA_DIR=$GEOSERVER_DATA_DIR -Dgeofence.dir=$GEOFENCE_DIR -DGEOSERVER_LOG_LOCATION=$GEOSERVER_LOG_LOCATION -DGEOWEBCACHE_CACHE_DIR=$GEOWEBCACHE_CACHE_DIR"

Those options could be updated or changed manually at any time, accordingly to your needs.


The default options we are going to add to the Servlet Container, assume you can reserve at least 4GB of RAM to GeoServer (see the option -Xmx4096m). You must be sure your machine has enough memory to run both GeoServer and GeoNode, which in this case means at least 4GB for GeoServer plus at least 2GB for GeoNode. A total of at least 6GB of RAM available on your machine. If you don’t have enough RAM available, you can lower down the values -Xms512m -Xmx4096m. Consider that with less RAM available, the performances of your services will be highly impacted.

In order to make the changes effective, you’ll need to restart the Servlet Container.

# Restart the server
sudo /etc/init.d/tomcat9 restart

# Follow the startup logs
sudo tail -F -n 300 /opt/data/geoserver_logs/geoserver.log

If you can see on the logs something similar to this, without errors

2019-05-31 10:06:34,190 INFO [geoserver.wps] - Found 5 bindable processes in GeoServer specific processes
2019-05-31 10:06:34,281 INFO [geoserver.wps] - Found 89 bindable processes in Deprecated processes
2019-05-31 10:06:34,298 INFO [geoserver.wps] - Found 31 bindable processes in Vector processes
2019-05-31 10:06:34,307 INFO [geoserver.wps] - Found 48 bindable processes in Geometry processes
2019-05-31 10:06:34,307 INFO [geoserver.wps] - Found 1 bindable processes in PolygonLabelProcess
2019-05-31 10:06:34,311 INFO [geoserver.wps] - Blacklisting process ras:ConvolveCoverage as the input kernel of type class cannot be handled
2019-05-31 10:06:34,319 INFO [geoserver.wps] - Blacklisting process ras:RasterZonalStatistics2 as the input zones of type class java.lang.Object cannot be handled
2019-05-31 10:06:34,320 INFO [geoserver.wps] - Blacklisting process ras:RasterZonalStatistics2 as the input nodata of type class it.geosolutions.jaiext.range.Range cannot be handled
2019-05-31 10:06:34,320 INFO [geoserver.wps] - Blacklisting process ras:RasterZonalStatistics2 as the input rangeData of type class java.lang.Object cannot be handled
2019-05-31 10:06:34,320 INFO [geoserver.wps] - Blacklisting process ras:RasterZonalStatistics2 as the output zonal statistics of type interface java.util.List cannot be handled
2019-05-31 10:06:34,321 INFO [geoserver.wps] - Found 18 bindable processes in Raster processes
2019-05-31 10:06:34,917 INFO [ows.OWSHandlerMapping] - Mapped URL path [/TestWfsPost] onto handler 'wfsTestServlet'
2019-05-31 10:06:34,918 INFO [ows.OWSHandlerMapping] - Mapped URL path [/wfs/*] onto handler 'dispatcher'
2019-05-31 10:06:34,918 INFO [ows.OWSHandlerMapping] - Mapped URL path [/wfs] onto handler 'dispatcher'
2019-05-31 10:06:42,237 INFO [] - Start reloading user/groups for service named default
2019-05-31 10:06:42,241 INFO [] - Reloading user/groups successful for service named default
2019-05-31 10:06:42,357 WARN [auth.GeoFenceAuthenticationProvider] - INIT FROM CONFIG
2019-05-31 10:06:42,494 INFO [] - AuthenticationCache Initialized with 1000 Max Entries, 300 seconds idle time, 600 seconds time to live and 3 concurrency level
2019-05-31 10:06:42,495 INFO [] - AuthenticationCache Eviction Task created to run every 600 seconds
2019-05-31 10:06:42,506 INFO [config.GeoserverXMLResourceProvider] - Found configuration file in /opt/data/gwc_cache_dir
2019-05-31 10:06:42,516 INFO [config.GeoserverXMLResourceProvider] - Found configuration file in /opt/data/gwc_cache_dir
2019-05-31 10:06:42,542 INFO [config.XMLConfiguration] - Wrote configuration to /opt/data/gwc_cache_dir
2019-05-31 10:06:42,547 INFO [geoserver.importer] - Enabling import store: memory

Your GeoServer should be up and running at



In case of errors or the file geoserver.log is not created, check the Catalina logs in order to try to understand what’s happened.

sudo less /opt/tomcat/latest/logs/catalina.out

5. Web Server

Until now we have seen how to start GeoNode in DEBUG mode from the command line, through the paver utilities. This is of course not the best way to start it. Moreover you will need a dedicated HTTPD server running on port 80 if you would like to expose your server to the world.

In this section we will see:

  1. How to configure NGINX HTTPD Server to host GeoNode and GeoServer. In the initial setup we will still run the services on http://localhost

  2. Update the settings in order to link GeoNode and GeoServer to the PostgreSQL Database.

  3. Update the settings in order to update GeoNode and GeoServer services running on a public IP or hostname.

  4. Install and enable HTTPS secured connection through the Let's Encrypt provider.

Install and configure NGINX


Seems to be possible that NGINX works with Python 3.6 and not with 3.8.

# Install the services
sudo apt install -y nginx uwsgi uwsgi-plugin-python3

Serving {“geonode”, “geoserver”} via NGINX

# Create the GeoNode UWSGI config
sudo vim /etc/uwsgi/apps-available/geonode.ini



Change the line virtualenv = /home/<my_user>/.virtualenvs/geonode below with your current user home directory!

e.g.: If the user is afabiani then virtualenv = /home/afabiani/.virtualenvs/geonode

uwsgi-socket =
# http-socket =

gid = www-data

plugins = python3
virtualenv = /home/<my_user>/.virtualenvs/geonode

env = DJANGO_SETTINGS_MODULE=geonode.settings

# #################
# backend
# #################
env = POSTGRES_USER=postgres
env = POSTGRES_PASSWORD=postgres
env = GEONODE_DATABASE=geonode
env = GEONODE_GEODATABASE=geonode_data
env = DATABASE_HOST=localhost
env = DATABASE_PORT=5432
env = DATABASE_URL=postgis://geonode:geonode@localhost:5432/geonode
env = GEODATABASE_URL=postgis://geonode:geonode@localhost:5432/geonode_data
env = BROKER_URL=amqp://admin:admin@localhost:5672//

env = SITEURL=http://localhost/

env = ALLOWED_HOSTS="['*']"

# Data Uploader
env = DEFAULT_BACKEND_UPLOADER=geonode.importer
env = HAYSTACK_ENGINE_URL=http://elasticsearch:9200/

# #################
# nginx
# HTTPD Server
# #################
env = GEONODE_LB_HOST_IP=localhost

# IP or domain name and port where the server can be reached on HTTPS (leave HOST empty if you want to use HTTP only)
# port where the server can be reached on HTTPS
env = HTTP_HOST=localhost

env = HTTP_PORT=8000
env = HTTPS_PORT=443

# #################
# geoserver
# #################
env = GEOSERVER_WEB_UI_LOCATION=http://localhost/geoserver/
env = GEOSERVER_PUBLIC_LOCATION=http://localhost/geoserver/
env = GEOSERVER_LOCATION=http://localhost:8080/geoserver/


# Java Options & Memory
env = ENABLE_JSONP=true
env = outFormat=text/javascript
env = GEOSERVER_JAVA_OPTS="-Djava.awt.headless=true -Xms2G -Xmx4G -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/var/log/jvm.log -XX:PerfDataSamplingInterval=500 -XX:SoftRefLRUPolicyMSPerMB=36000 -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=4 -Dfile.encoding=UTF8 -Djavax.servlet.request.encoding=UTF-8 -Djavax.servlet.response.encoding=UTF-8 -Duser.timezone=GMT -Dorg.geotools.shapefile.datetime=false -DGS-SHAPEFILE-CHARSET=UTF-8 -DGEOSERVER_CSRF_DISABLED=true -DPRINT_BASE_URL=http://geoserver:8080/geoserver/pdf -DALLOW_ENV_PARAMETRIZATION=true -Xbootclasspath/a:/usr/local/tomcat/webapps/geoserver/WEB-INF/lib/marlin-0.9.3-Unsafe.jar -Dsun.java2d.renderer=org.marlin.pisces.MarlinRenderingEngine"

# #################
# Security
# #################
# Admin Settings
env = ADMIN_USERNAME=admin
env = ADMIN_PASSWORD=admin
env = ADMIN_EMAIL=admin@localhost

# EMAIL Notifications
env = EMAIL_ENABLE=False
env = DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
env = DJANGO_EMAIL_HOST=localhost
env = DEFAULT_FROM_EMAIL='GeoNode <>'

# Session/Access Control

# Users Registration

# OAuth2
env = OAUTH2_CLIENT_ID=Jrchz2oPY3akmzndmgUTYrs9gczlgoV20YPSvqaV
env = OAUTH2_CLIENT_SECRET=rCnp5txobUo83EpQEblM8fVj3QT5zb5qRfxNsuPzCqZaiRyIoxM4jdgMiZKFfePBHYXCLd7B8NlkfDBY9HKeIQPcy5Cp08KQNpRHQbjpLItDHv12GvkSeXp6OxaUETv3

# GeoNode APIs
env = API_LOCKDOWN=False

# #################
# Production and
# Monitoring
# #################
env = DEBUG=False

env = SECRET_KEY='myv-y4#7j-d*p-__@j#*3z@!y24fz8%^z2v6atuy4bo9vqr1_a'


env = MEMCACHED_BACKEND=django.core.cache.backends.memcached.MemcachedCache


# GIS Client

# Monitoring
env = MONITORING_SERVICE_NAME=local-geonode

# Other Options/Contribs

chdir = /opt/geonode
module = geonode.wsgi:application

strict = false
master = true
enable-threads = true
vacuum = true                        ; Delete sockets during shutdown
single-interpreter = true
die-on-term = true                   ; Shutdown when receiving SIGTERM (default is respawn)
need-app = true

# logging
# path to where uwsgi logs will be saved
logto = /opt/data/logs/geonode.log
daemonize = /opt/data/logs/geonode.log
touch-reload = /opt/geonode/geonode/
buffer-size = 32768

harakiri = 60                        ; forcefully kill workers after 60 seconds
py-callos-afterfork = true           ; allow workers to trap signals

max-requests = 1000                  ; Restart workers after this many requests
max-worker-lifetime = 3600           ; Restart workers after this many seconds
reload-on-rss = 2048                 ; Restart workers after this much resident memory
worker-reload-mercy = 60             ; How long to wait before forcefully killing workers

cheaper-algo = busyness
processes = 128                      ; Maximum number of workers allowed
cheaper = 8                          ; Minimum number of workers allowed
cheaper-initial = 16                 ; Workers created at startup
cheaper-overload = 1                 ; Length of a cycle in seconds
cheaper-step = 16                    ; How many workers to spawn at a time

cheaper-busyness-multiplier = 30     ; How many cycles to wait before killing workers
cheaper-busyness-min = 20            ; Below this threshold, kill workers (if stable for multiplier cycles)
cheaper-busyness-max = 70            ; Above this threshold, spawn new workers
cheaper-busyness-backlog-alert = 16  ; Spawn emergency workers if more than this many requests are waiting in the queue
cheaper-busyness-backlog-step = 2    ; How many emergency workers to create if there are too many requests in the queue
# Enable the GeoNode UWSGI config
sudo ln -s /etc/uwsgi/apps-available/geonode.ini /etc/uwsgi/apps-enabled/geonode.ini

# Restart UWSGI Service
sudo pkill -9 -f uwsgi
# Create the UWSGI system service

# Create the executable
sudo vim /usr/bin/

  sudo uwsgi --ini /etc/uwsgi/apps-enabled/geonode.ini

sudo chmod +x /usr/bin/

# Create the systemctl Service
sudo vim /etc/systemd/system/geonode-uwsgi.service
Description=GeoNode UWSGI Service


# Enable the UWSGI service
sudo systemctl daemon-reload
sudo systemctl start geonode-uwsgi.service
sudo systemctl status geonode-uwsgi.service
sudo systemctl enable geonode-uwsgi.service
# Backup the original NGINX config
sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig

# Create the GeoNode Default NGINX config
sudo vim /etc/nginx/nginx.conf
# Make sure your nginx.config matches the following one
user www-data;
worker_processes auto;
pid /run/;
include /etc/nginx/modules-enabled/*.conf;

events {
  worker_connections 768;
  # multi_accept on;

http {
  # Basic Settings

  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;
  # server_tokens off;

  # server_names_hash_bucket_size 64;
  # server_name_in_redirect off;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  # SSL Settings

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
  ssl_prefer_server_ciphers on;

  # Logging Settings

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  # Gzip Settings

  gzip on;
  gzip_vary on;
  gzip_proxied any;
  gzip_http_version 1.1;
  gzip_disable "MSIE [1-6]\.";
  gzip_buffers 16 8k;
  gzip_min_length 1100;
  gzip_comp_level 6;
  gzip_types video/mp4 text/plain application/javascript application/x-javascript text/javascript text/xml text/css image/jpeg;

  # Virtual Host Configs

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
# Remove the Default NGINX config
sudo rm /etc/nginx/sites-enabled/default

# Create the GeoNode App NGINX config
sudo vim /etc/nginx/sites-available/geonode
uwsgi_intercept_errors on;

upstream geoserver_proxy {
  server localhost:8080;

# Expires map
map $sent_http_content_type $expires {
  default                    off;
  text/html                  epoch;
  text/css                   max;
  application/javascript     max;
  ~image/                    max;

server {
  listen 80 default_server;
  listen [::]:80 default_server;

  root /var/www/html;
  index index.html index.htm index.nginx-debian.html;

  server_name _;

  charset utf-8;

  etag on;
  expires $expires;
  proxy_read_timeout 600s;
  # set client body size to 2M #
  client_max_body_size 50000M;

  location / {
    etag off;
    uwsgi_read_timeout 600s;
    include uwsgi_params;

  location /static/ {
    alias /opt/geonode/geonode/static_root/;

  location /uploaded/ {
    alias /opt/geonode/geonode/uploaded/;

  location /geoserver {
    proxy_pass http://geoserver_proxy;
    include proxy_params;
# Prepare the uploaded folder
sudo mkdir -p /opt/geonode/geonode/uploaded
sudo chown -Rf tomcat:www-data /opt/geonode/geonode/uploaded
sudo chmod -Rf 777 /opt/geonode/geonode/uploaded/

sudo touch /opt/geonode/geonode/.celery_results
sudo chmod 777 /opt/geonode/geonode/.celery_results

# Enable GeoNode NGINX config
sudo ln -s /etc/nginx/sites-available/geonode /etc/nginx/sites-enabled/geonode

# Restart the services
sudo service tomcat9 restart
sudo service nginx restart

Update the settings in order to use the PostgreSQL Database


Make sure you already installed and configured the Database as explained in the previous sections.


Instead of using the, you can drive the GeoNode behavior through the .env* variables; see as an instance the file ./ or ./ in order to understand how to use them. In that case you don’t need to create the file; you can just stick with the decault one, which will take the values from the ENV. We tend to prefer this method in a production/dockerized system.

workon geonode
cd /opt/geonode

# Initialize GeoNode
chmod +x *.sh
./ reset
./ setup
./ sync
./ collectstatic --noinput
sudo chmod -Rf 777 geonode/static_root/ geonode/uploaded/

Before finalizing the configuration we will need to update the UWSGI settings

Restart UWSGI and update OAuth2 by using the new geonode.settings

# As superuser
sudo su

# Restart Tomcat
service tomcat9 restart

# Restart UWSGI
pkill -9 -f uwsgi

# Update the GeoNode ip or hostname
cd /opt/geonode

# This must be done the first time only
cp package/support/geonode.binary /usr/bin/geonode
cp package/support/geonode.updateip /usr/bin/geonode_updateip
chmod +x /usr/bin/geonode
chmod +x /usr/bin/geonode_updateip

# Refresh GeoNode and GeoServer OAuth2 settings
source .env_local
PYTHONWARNINGS=ignore VIRTUAL_ENV=$VIRTUAL_ENV DJANGO_SETTINGS_MODULE=geonode.settings GEONODE_ETC=/opt/geonode/geonode GEOSERVER_DATA_DIR=/opt/data/geoserver_data TOMCAT_SERVICE="service tomcat9" APACHE_SERVICE="service nginx" geonode_updateip -p localhost

# Go back to standard user

Check for any error with

sudo tail -F -n 300 /var/log/uwsgi/app/geonode.log

Reload the UWSGI configuration with

touch /opt/geonode/geonode/

6. Update the settings in order to update GeoNode and GeoServer services running on a public IP or hostname


Before exposing your services to the Internet, make sure your system is hardened and secure enough. See the specific documentation section for more details.

Let’s say you want to run your services on a public IP or domain, e.g. You will need to slightly update your services in order to reflect the new server name.

In particular the steps to do are:

  1. Update NGINX configuration in order to serve the new domain name.

sudo vim /etc/nginx/sites-enabled/geonode

# Update the 'server_name' directive

# Restart the service
sudo service nginx restart
  1. Update UWSGI configuration in order to serve the new domain name.

sudo vim /etc/uwsgi/apps-enabled/geonode.ini

# Change everywhere 'localhost' to the new hostname

# Restart the service
sudo service geonode-uwsgi restart
  1. Update OAuth2 configuration in order to hit the new hostname.

workon geonode
sudo su
cd /opt/geonode

# Update the GeoNode ip or hostname
PYTHONWARNINGS=ignore VIRTUAL_ENV=$VIRTUAL_ENV DJANGO_SETTINGS_MODULE=geonode.local_settings GEONODE_ETC=/opt/geonode/geonode GEOSERVER_DATA_DIR=/opt/data/geoserver_data TOMCAT_SERVICE="service tomcat9" APACHE_SERVICE="service nginx" geonode_updateip -l localhost -p

  1. Update the existing GeoNode links in order to hit the new hostname.

workon geonode

# To avoid spatialite conflict if using postgresql
vim $VIRTUAL_ENV/bin/postactivate

# Add these to make available. Change user, password and server information to yours
export DATABASE_URL='postgresql://<postgresqluser>:<postgresqlpass>@localhost:5432/geonode'

#Close virtual environmetn and aopen it again to update variables

workon geonode
cd /opt/geonode

# Update the GeoNode ip or hostname
DJANGO_SETTINGS_MODULE=geonode.local_settings python migrate_baseurl --source-address=http://localhost --target-address=


If at the end you get a “bad gateway” error when accessing your geonode site, check uwsgi log with sudo tail -f /var/log/uwsgi/app/geonode.log and if theres is an error related with port 5432 check the listening configuration from the postgresql server and allow the incoming traffic from geonode.

7. Install and enable HTTPS secured connection through the Let’s Encrypt provider

# Install Let's Encrypt Certbot
# sudo add-apt-repository ppa:certbot/certbot  # for ubuntu 18.04 and lower
sudo apt update -y; sudo apt install python3-certbot-nginx -y

# Reload NGINX config and make sure the firewall denies access to HTTP
sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

# Create and dump the Let's Encrypt Certificates
sudo certbot --nginx -d -d
# ...choose the redirect option when asked for

Next, the steps to do are:

  1. Update the GeoNode OAuth2 Redirect URIs accordingly.

From the GeoNode Admin Dashboard go to Home Django/GeoNode OAuth Toolkit Applications GeoServer


Redirect URIs

  1. Update the GeoServer Proxy Base URL accordingly.

From the GeoServer Admin GUI go to About & Status > Global


Proxy Base URL

  1. Update the GeoServer Role Base URL accordingly.

From the GeoServer Admin GUI go to Security > Users, Groups, Roles > geonode REST role service


Role Base URL

  1. Update the GeoServer OAuth2 Service Parameters accordingly.

From the GeoServer Admin GUI go to Security > Authentication > Authentication Filters > geonode-oauth2


OAuth2 Service Parameters

  1. Update the UWSGI configuration

sudo vim /etc/uwsgi/apps-enabled/geonode.ini

# Change everywhere 'http' to 'https'

# Add three more 'env' variables to the configuration

# Restart the service
sudo service geonode-uwsgi restart

UWSGI Configuration

8. Enabling Fully Asynchronous Tasks

Install and configure “rabbitmq-server”

See also

A March 2021 blog post from RabbitMQ provides alternative installations for other systems.

Install rabbitmq-server

Reference: &

sudo apt install curl -y

## Import GPG Key
sudo apt update
sudo apt install curl software-properties-common apt-transport-https lsb-release
curl -fsSL | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/erlang.gpg

## Add Erlang Repository to Ubuntu
sudo apt update
sudo apt install erlang

## Add RabbitMQ Repository to Ubuntu
curl -s | sudo bash

## Install RabbitMQ Server
sudo apt install rabbitmq-server

# check the status (it should already be running)
sudo systemctl status rabbitmq-server

# check the service is enabled (it should already be enabled)
sudo systemctl is-enabled rabbitmq-server.service

# enable the web frontend and allow access through firewall
# view this interface at http://<your ip>:15672
sudo rabbitmq-plugins enable rabbitmq_management
sudo ufw allow proto tcp from any to any port 5672,15672

Create admin user

This is the user that GeoNode will use to communicate with rabbitmq-server.

sudo rabbitmqctl delete_user guest
sudo rabbitmqctl add_user admin <your_rabbitmq_admin_password_here>
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl add_vhost /localhost
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
sudo rabbitmqctl set_permissions -p /localhost admin ".*" ".*" ".*"

Managing RabbitMQ

You can manage the rabbitmq-server service like any other service:

sudo systemctl stop rabbitmq-server
sudo systemctl start rabbitmq-server
sudo systemctl restart rabbitmq-server

You can manage the rabbitmq-server node with rabbitmqctl. For example, to fully reset the server, use these commands:

sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl start_app

After reset, you’ll need to recreate the admin user (see above).

Install and configure “supervisor” and “celery”

Install supervisor

sudo apt install supervisor

sudo mkdir /etc/supervisor
echo_supervisord_conf > /etc/supervisor/supervisord.conf

sudo mkdir /etc/supervisor/conf.d

Configure supervisor

sudo vim /etc/supervisor/supervisord.conf
; supervisor config file

file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                       ; sockef file mode (default 0700)

logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/ ; (supervisord pidfile;default
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

files = /etc/supervisor/conf.d/*.conf

Note the last line which includes the geonode-celery.conf file that is described below.

Set the `environment` directive

Environment variables are placed directly into the /etc/supervisor/supervisord.conf file; they are exposed to the service via the environment directive.

The syntax of this directive can either be all on one line like this (shown above):


or broken into multiple indented lines like this:


The following are the minimum set of env key value pairs you will need for a standard GeoNode Celery instance:


  • BROKER_URL="amqp://admin:<your_rabbitmq_admin_password_here>@localhost:5672/"













  • These key value pairs must match the values you have already set on the uwsgi.ini file.

  • If you have custom tasks that use any other variables from django.conf.settings (like MEDIA_ROOT), these variables must also be added to the environment directive.

Configure celery

sudo vim /etc/supervisor/conf.d/geonode-celery.conf
command = sh -c "/<full_path_to_the_virtuaenv>/bin/celery -A geonode.celery_app:app worker -B -E --loglevel=DEBUG --concurrency=10 -n worker1@%%h"
directory = /<full_path_to_the_geonode_source_code>
autostart = true
autorestart = true
startsecs = 10
stopwaitsecs = 600
priority = 998

Manage supervisor and celery

Reload and restart supervisor and the celery workers

# Restart supervisor
sudo supervisorctl reload
sudo systemctl restart supervisor

# Kill old celery workers (if any)
sudo pkill -f celery

Make sure everything is green

# Check the supervisor service status
sudo systemctl status supervisor

# Check the celery workers logs
sudo tail -F -n 300 /var/logs/geonode-celery.log

Install and configure “memcached”

sudo apt install memcached

sudo systemctl start memcached
sudo systemctl enable memcached

workon <your_geonode_venv_name>
cd /<full_path_to_the_geonode_source_code>

sudo apt install libmemcached-dev zlib1g-dev

pip install pylibmc==1.6.1
pip install sherlock==0.3.2

sudo systemctl restart supervisor.service
sudo systemctl status supervisor.service


In this section we are going to list the passages needed to deploy a vanilla GeoNode with Docker You can follow the instructions at Docker Setup for Ubuntu (20.04) to prepare a Ubuntu 22.04 server with Docker and Docker Compose

1. Clone GeoNode

# Let's create the GeoNode core base folder and clone it
sudo mkdir -p /opt/geonode/
sudo usermod -a -G www-data geonode
sudo chown -Rf geonode:www-data /opt/geonode/
sudo chmod -Rf 775 /opt/geonode/

# Clone the GeoNode source code on /opt/geonode
cd /opt
git clone

2. Prepare the .env file

Follow the instructions at Docker create env file

3. Build and run

Follow the instructions at Docker build and run

Test the instance and follow the logs

If you run the containers daemonized (with the -d option), you can either run specific Docker commands to follow the startup and initialization logs or entering the image shell and check for the GeoNode logs.

In order to follow the startup and initialization logs, you will need to run the following command from the repository folder

cd /opt/geonode
docker logs -f django4geonode


cd /opt/geonode
docker-compose logs -f django

You should be able to see several initialization messages. Once the container is up and running, you will see the following statements

789 static files copied to '/mnt/volumes/statics/static'.
static data refreshed
Executing UWSGI server uwsgi --ini /usr/src/app/uwsgi.ini for Production
[uWSGI] getting INI configuration from /usr/src/app/uwsgi.ini

To exit just hit CTRL+C.

This message means that the GeoNode containers have bee started. Browsing to http://localhost/ will show the GeoNode home page. You should be able to successfully log with the default admin user (admin / admin) and start using it right away.

With Docker it is also possible to run a shell in the container and follow the logs exactly the same as you deployed it on a physical host. To achieve this run

docker exec -it django4geonode /bin/bash

# Once logged in the GeoNode image, follow the logs by executing
tail -F -n 300 /var/log/geonode.log


docker-compose exec django /bin/bash

To exit just hit CTRL+C and exit to return to the host.

Override the ENV variables to deploy on a public IP or domain

If you would like to start the containers on a public IP or domain, let’s say, you can follow the instructions at Deploy to production

ariables to customize the GeoNode instance. See the GeoNode Settings section in order to get a list of the available options.

Remove all data and bring your running GeoNode deployment to the initial stage

This procedure allows you to stop all the containers and reset all the data with the deletion of all the volumes.

cd /opt/geonode

# stop containers and remove volumes
docker-compose down -v

Get rid of old Docker images and volumes (reset the environment completely)


For more details on Docker commands, please refer to the official Docker documentation.

It is possible to let docker show which containers are currently running (add -a for all containers, also stopped ones)

# Show the currently running containers
docker ps

CONTAINER ID   IMAGE                      COMMAND                  CREATED          STATUS                   PORTS                                                                      NAMES
4729b3dd1de7   geonode/geonode:4.0        "/usr/src/geonode/en…"   29 minutes ago   Up 5 minutes             8000/tcp                                                                   celery4geonode
418da5579b1a   geonode/geonode:4.0        "/usr/src/geonode/en…"   29 minutes ago   Up 5 minutes (healthy)   8000/tcp                                                                   django4geonode
d6b043f16526   geonode/letsencrypt:4.0    "./docker-entrypoint…"   29 minutes ago   Up 9 seconds             80/tcp, 443/tcp                                                            letsencrypt4geonode
c77e1fa3ab2b   geonode/geoserver:2.19.6   "/usr/local/tomcat/t…"   29 minutes ago   Up 5 minutes (healthy)   8080/tcp                                                                   geoserver4geonode
a971cedfd788   rabbitmq:3.7-alpine        "docker-entrypoint.s…"   29 minutes ago   Up 5 minutes             4369/tcp, 5671-5672/tcp, 25672/tcp                                         rabbitmq4geonode
a2e4c69cb80f   geonode/nginx:4.0          "/docker-entrypoint.…"   29 minutes ago   Up 5 minutes   >80/tcp, :::80->80/tcp,    >443/tcp, :::443->443/tcp   nginx4geonode
d355d34cac4b   geonode/postgis:13         "docker-entrypoint.s…"   29 minutes ago   Up 5 minutes             5432/tcp                                                                   db4geonode

Stop all the containers by running

docker-compose stop

Force kill all containers by running

docker kill $(docker ps -q)

I you want to clean up all containers and images, without deleting the static volumes (i.e. the DB and the GeoServer catalog), issue the following commands

# Remove all containers
docker rm $(docker ps -a -q)

# Remove all docker images
docker rmi $(docker images -q)

# Prune the old images
docker system prune -a

If you want to remove a volume also

# List of the running volumes
docker volume ls

# Remove the GeoServer catalog by its name
docker volume rm -f geonode-gsdatadir

# Remove all dangling docker volumes
docker volume rm $(docker volume ls -qf dangling=true)

# update all images, should be run regularly to fetch published updates
for i in $(docker images| awk 'NR>1{print $1":"$2}'| grep -v '<none>'); do docker pull "$i" ;done