diff --git a/warden_server/README.test b/warden_server/README.test
index 36246a7049e859f9ae3b360c02f219e5a92f6263..af6b90f53d7317524a5ee8c7953624efcf1bdf04 100644
--- a/warden_server/README.test
+++ b/warden_server/README.test
@@ -21,7 +21,8 @@ B. Compatibility
 
 * The test suite, just like the Warden Server, is compatible with both Python2
   (tested on 2.7) and Python3 (tested on 3.6).
-* Just like Warden Server, the test suite requires a local MySQL installation.
+* Just like Warden Server, the test suite requires a local MySQL or PostgreSQL
+  installation.
 * It is safe to run the test suite on a production system. For testing,
   a database distinct from the default production one is used. Also, the user
   account used for accessing the testing database is set for local login only.
@@ -56,7 +57,11 @@ D. Usage
 Before running the tests (for the first time), a DB user with required rights
 must be created. An easy way to do it is:
 	./test_warden_server.py --init
-This will prompt for MySQL root password.
+This will prompt for the database administrator account ('root' for MySQL and
+'postgres' for PostgreSQL) password. Please note that by default, the user
+'postgres' can only be authenticated using the peer authentication method,
+which requires that the script is run by the operating system user 'postgres'.
+When this is the case, the password is not required.
 
 Standard usage for testing:
 	./test_warden_server.py
@@ -64,16 +69,22 @@ Standard usage for testing:
 Advanced usage:
 
 	./test_warden_server.py --help
-	usage: test_warden_server.py [-h] [-i] [-n]
+	usage: test_warden_server.py [-h] [-i] [-n] [-d {MySQL,PostgreSQL}]
 
 	Warden3 Server Test Suite
 
 	optional arguments:
-	  -h, --help     show this help message and exit
-	  -i, --init     Set up an user with rights to CREATE/DROP the
-			 test database
-	  -n, --nopurge  Skip the database purge after running the tests
-
+	  -h, --help            show this help message and exit
+	  -d {MySQL,PostgreSQL}, --dbms {MySQL,PostgreSQL}
+	                        Database management system to use for
+	                        testing
+	  -i, --init            Set up an user with rights to
+	                        CREATE/DROP the test database
+	  -n, --nopurge         Skip the database purge after running
+	                        the tests
+
+Option -d (--dbms) sets the databse management system to use for testing.
+If this option is not provided, MySQL is used as default.
 
 Option -n (--nopurge) is meant for debugging purposes and test development, it
 keeps the test database around for inspection after running the tests.
diff --git a/warden_server/test_warden_server.py b/warden_server/test_warden_server.py
index 09376e1e4fc44b0505c6edee1e56d7c0be75a753..915db505ed5649c4b058d4caab018d065b47182c 100755
--- a/warden_server/test_warden_server.py
+++ b/warden_server/test_warden_server.py
@@ -6,6 +6,7 @@ import argparse
 import getpass
 import sys
 import warnings
+import json
 from os import path
 from warden_server import build_server
 import warden_server
@@ -486,8 +487,178 @@ class MySQL:
         conn.close()
 
 
+class PostgreSQL:
+    name = "PostgreSQL"
+    reg_mod_test_query = "SELECT requestor, hostname, name, secret, valid, clients.read, " \
+                         "debug, clients.write, test, note FROM clients WHERE id = %s"
+
+    def __init__(self, user=USER, password=PASSWORD, dbname=DB):
+        import psycopg2 as ppg
+        from psycopg2 import sql as ppgsql
+        self.ppg = ppg
+        self.ppgsql = ppgsql
+
+        self.user = user
+        self.password = password
+        self.dbname = dbname
+
+    def init_user(self):
+        """DB user rights setup"""
+        running_as_postgres = getpass.getuser() == "postgres"
+        conn = None
+        try:
+            password = None if running_as_postgres else getpass.getpass("Enter PostgreSQL password for the user 'postgres':")
+            conn = self.ppg.connect(user="postgres", password=password)
+            with conn.cursor() as cur:
+                cur.execute(
+                    self.ppgsql.SQL("CREATE ROLE {} PASSWORD {} CREATEDB LOGIN").format(
+                        self.ppgsql.Identifier(self.user),
+                        self.ppgsql.Placeholder()
+                    ),
+                    (self.password,)
+                )
+            conn.commit()
+            print("DB User set up successfuly")
+        except self.ppg.OperationalError as ex:
+            if conn:
+                conn.rollback()
+                conn.close()
+                conn = None
+            if running_as_postgres:
+                print("Connection unsuccessful. Original exception: %s" % (str(ex)))
+            else:
+                print("Connection unsuccessful, bad password or meant to run as the user 'postgres'"
+                      " (su postgres -c '%s --dbms PostgreSQL --init')? Original exception: %s" %
+                      (path.join('.', path.normpath(sys.argv[0])), str(ex)))
+            exit()
+        except KeyboardInterrupt:
+            print("\nCancelled!")
+            exit()
+        finally:
+            if conn:
+                conn.close()
+
+    def _load_tags(self, cur):
+        with open(path.join(path.dirname(__file__), "tagmap_db.json"),
+                  encoding="utf8") as tagmapf:
+            tagmap = json.load(tagmapf)
+        for tag, num in tagmap.items():
+            cur.execute(
+                "INSERT INTO tags(id, tag) VALUES (%s, %s)", (num, tag))
+
+    def _load_cats(self, cur):
+        with open(path.join(path.dirname(__file__), "catmap_db.json"),
+                  encoding="utf8") as catmapf:
+            catmap = json.load(catmapf)
+        for cat_subcat, num in catmap.items():
+            catsplit = cat_subcat.split(".", 1)
+            category = catsplit[0]
+            subcategory = catsplit[1] if len(catsplit) > 1 else None
+            cur.execute(
+                "INSERT INTO categories(id, category, subcategory, cat_subcat) VALUES (%s, %s, %s, %s)",
+                (num, category, subcategory, cat_subcat)
+            )
+
+    def set_up(self):
+        conn = None
+        try:
+            conn = self.ppg.connect(user=self.user, password=self.password,
+                                    host='localhost', dbname='postgres')
+            conn.autocommit = True
+            cur = conn.cursor()
+            cur.execute(
+                self.ppgsql.SQL("DROP DATABASE IF EXISTS {}").format(
+                    self.ppgsql.Identifier(self.dbname)
+                )
+            )
+            cur.execute(
+                self.ppgsql.SQL("CREATE DATABASE {}").format(
+                    self.ppgsql.Identifier(self.dbname)
+                )
+            )
+        except self.ppg.OperationalError as ex:
+            if conn:
+                conn.rollback()
+                conn.close()
+                conn = None
+            print(
+                'Setup failed, have you tried --init ? Original exception: %s' % (str(ex),))
+            exit()
+        finally:
+            if conn:
+                conn.close()
+        conn = None
+        try:
+            conn = self.ppg.connect(user=self.user, password=self.password,
+                                    dbname=self.dbname, host='localhost')
+            cur = conn.cursor()
+            with open(path.join(path.dirname(__file__), 'warden_3.0_postgres.sql'),
+                      encoding="utf8") as script:
+                statements = script.read()
+            cur.execute(statements)
+
+            self._load_tags(cur)
+            self._load_cats(cur)
+
+            cur.execute(
+                "INSERT INTO clients "
+                "(registered, requestor, hostname, note, valid,"
+                " name, secret, read, debug, write, test) "
+                "VALUES(NOW(), 'warden-info@cesnet.cz', 'test.server.warden.cesnet.cz', "
+                       "NULL, 1, 'cz.cesnet.warden3test', 'abc', 1, 1, 1, 0)"
+            )
+            conn.commit()
+        except self.ppg.OperationalError as ex:
+            if conn:
+                conn.rollback()
+                conn.close()
+                conn = None
+            print(
+                'Something went wrong during database setup. Original exception: %s' % (str(ex),))
+            exit()
+        finally:
+            if conn:
+                conn.close()
+
+    def do_sql_select(self, query, params):
+        """Reads data from database"""
+        conn = self.ppg.connect(user=self.user, password=self.password,
+                                dbname=self.dbname, host='localhost')
+        cur = conn.cursor()
+        cur.execute(query, params)
+        result = cur.fetchall()
+        cur.close()
+        conn.close()
+        return tuple(result)
+
+    def tear_down(self):
+        """Clean up by purging the test database"""
+        conn = self.ppg.connect(user=self.user, password=self.password,
+                                dbname='postgres', host='localhost')
+        conn.autocommit = True
+        cur = conn.cursor()
+        cur.execute(
+            self.ppgsql.SQL("DROP DATABASE IF EXISTS {} WITH(FORCE)").format(
+                self.ppgsql.Identifier(self.dbname)
+            )
+        )
+        conn.close()
+
+    def clean_lastid(self):
+        """Cleans the lastid information for all clients"""
+        conn = self.ppg.connect(
+            user=self.user, password=self.password, dbname=self.dbname, host='localhost')
+        cur = conn.cursor()
+        cur.execute("DELETE FROM last_events")
+        cur.execute("DELETE FROM events")
+        cur.close()
+        conn.commit()
+        conn.close()
+
+
 database_types = {
-    'MySQL': MySQL
+    'MySQL': MySQL,
+    'PostgreSQL': PostgreSQL
 }