In the last post we’ve looked at how you you get EDB Postgres Extended server installed on your system. In contrast to the version provided by Fujitsu, TDE needs to be enabled when you initialize a new cluster, so lets do this and see how we can proceed from there on.
When we’ve initialized a new cluster in the last post, we got this output from initdb:
Data page checksums are enabled.
Transparent data encryption is disabled.
Somehow we need to enable this and the EDB’s version of initdb comes with this options when dealing with TDE:
[postgres@postgres-tde ~]$ /usr/edb/pge18/bin/initdb --help | egrep -A 2 "encryption|key"
-y, --data-encryption[=KEYLEN]
enable transparent data encryption
--copy-key-from=FILE copy key from given location (useful for upgrades)
--key-wrap-command=CMD
shell command to wrap (encrypt) data encryption key
--key-unwrap-command=CMD
shell command to unwrap (decrypt) data encryption key
--no-key-wrap do not wrap the generated data encryption key (insecure!)
“-y, –data-encryption” is apparently about enabling encryption in general and then there are a couple of options around a key. This key is very much like how Fujitsu is doing it with the keystore. For production deployments you should retrieve the key from an external keystore and you need to provide the “–key-wrap-command” and “–key-unwrap-command” switches for this. There is also the “–no-key-wrap” but this leaves your key unprotected and should not be used at all. For the scope of this post we’ll protect the key with a passphrase:
# cleanup from previous post
[postgres@postgres-tde ~]$ rm -rf /u02/pgdata/pge18/
# initialize a new cluster with TDE enabled
[postgres@postgres-tde ~]$ sudo PGSETUP_INITDB_OPTIONS="--encoding UTF-8 --pgdata=/u02/pgdata/pge18 -y --key-wrap-command='openssl enc -e -aes-128-cbc -pbkdf2 -out \"%p\"' --key-unwrap-command='openssl enc -d -aes-128-cbc -pbkdf2 -in \"%p\"'" /usr/edb/pge18/bin/edb-pge-18-setup initdb
Initializing database ...
This does not give much output and actually nothing was done. Lets use initdb directly without using the “db-pge-18-setup” wrapper:
[postgres@postgres-tde ~]$ /usr/edb/pge18/bin/initdb --encoding UTF-8 --pgdata=/u02/pgdata/pge18 -y --key-wrap-command='openssl enc -e -aes-128-cbc -pbkdf2 -out "%p"' --key-unwrap-command='openssl enc -d -aes-128-cbc -pbkdf2 -in "%p"'
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.UTF-8".
The default text search configuration will be set to "english".
Data page checksums are enabled.
Transparent data encryption is enabled (128 bits).
creating directory /u02/pgdata/pge18 ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Europe/Berlin
creating configuration files ... ok
setting up data encryption ... enter AES-128-CBC encryption password:
Verifying - enter AES-128-CBC encryption password:
ok
running bootstrap script ... enter AES-128-CBC decryption password:
ok
performing post-bootstrap initialization ... enter AES-128-CBC decryption password:
ok
syncing data to disk ... ok
initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
Success. You can now start the database server using:
/usr/edb/pge18/bin/pg_ctl -D /u02/pgdata/pge18 -l logfile start
This will ask for the encryption password to be used and generates/populates PGDATA:
[postgres@postgres-tde ~]$ ls -la /u02/pgdata/pge18/
total 60
drwx------. 20 postgres postgres 4096 Feb 9 07:52 .
drwxr-xr-x. 3 postgres postgres 19 Feb 9 07:52 ..
drwx------. 5 postgres postgres 33 Feb 9 07:52 base
drwx------. 2 postgres postgres 4096 Feb 9 07:52 global
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_commit_ts
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_dynshmem
drwx------. 2 postgres postgres 21 Feb 9 07:52 pg_encryption
-rw-------. 1 postgres postgres 5721 Feb 9 07:52 pg_hba.conf
-rw-------. 1 postgres postgres 2681 Feb 9 07:52 pg_ident.conf
drwx------. 4 postgres postgres 68 Feb 9 07:52 pg_logical
drwx------. 4 postgres postgres 36 Feb 9 07:52 pg_multixact
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_notify
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_replslot
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_serial
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_snapshots
drwx------. 2 postgres postgres 25 Feb 9 07:52 pg_stat
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_stat_tmp
drwx------. 2 postgres postgres 18 Feb 9 07:52 pg_subtrans
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_tblspc
drwx------. 2 postgres postgres 6 Feb 9 07:52 pg_twophase
-rw-------. 1 postgres postgres 3 Feb 9 07:52 PG_VERSION
drwx------. 4 postgres postgres 77 Feb 9 07:52 pg_wal
drwx------. 2 postgres postgres 18 Feb 9 07:52 pg_xact
-rw-------. 1 postgres postgres 88 Feb 9 07:52 postgresql.auto.conf
-rw-------. 1 postgres postgres 32732 Feb 9 07:52 postgresql.conf
What this did in addition is to add the unwrap command to postgresql.conf:
[postgres@postgres-tde ~]$ grep wrap "/u02/pgdata/pge18/postgresql.conf"
data_encryption_key_unwrap_command = 'openssl enc -d -aes-128-cbc -pbkdf2 -in "%p"'
Time to start up. When we did the installation in the last post the rpm created a systemd service definition:
[postgres@postgres-tde ~]$ systemctl list-unit-files | grep edb
edb-pge-18.service disabled disabled
Of course this service points to the wrong PGDATA so we need to adjust this:
[postgres@postgres-tde ~]$ grep PGDATA\= /usr/lib/systemd/system/edb-pge-18.service
Environment=PGDATA=/var/lib/edb-pge/18/data/
[postgres@postgres-tde ~]$ sudo systemctl edit edb-pge-18.service
### Editing /etc/systemd/system/edb-pge-18.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file
[service]
Environment=PGDATA=/u02/pgdata/pge18/
...
Trying to start PostgreSQL afterwards fails, because the decryption password cannot be retrieved:
[postgres@postgres-tde ~]$ sudo systemctl start edb-pge-18.service
Job for edb-pge-18.service failed because the control process exited with error code.
See "systemctl status edb-pge-18.service" and "journalctl -xeu edb-pge-18.service" for details.
[postgres@postgres-tde ~]$ sudo journalctl -u edb-pge-18.service
Feb 09 08:05:45 postgres-tde systemd[1]: Starting Postgres Extended 18 database server...
Feb 09 08:05:45 postgres-tde postgres[5303]: enter AES-128-CBC decryption password:
Feb 09 08:05:45 postgres-tde postgres[5303]: bad password read
Feb 09 08:05:45 postgres-tde postgres[5303]: 4027EF83CA7F0000:error:1400006B:UI routines:UI_process:processing error:crypto/ui/ui_lib.c:552:while reading strings
Feb 09 08:05:45 postgres-tde postgres[5302]: 2026-02-09 08:05:45.875 CET [5302] FATAL: could not run command "openssl enc -d -aes-128-cbc -pbkdf2 -in "pg_encryption/key.bin"": child process exited with exit code 1
Feb 09 08:05:45 postgres-tde postgres[5302]: 2026-02-09 08:05:45.875 CET [5302] LOG: database system is shut down
Feb 09 08:05:45 postgres-tde systemd[1]: edb-pge-18.service: Main process exited, code=exited, status=1/FAILURE
Feb 09 08:05:45 postgres-tde systemd[1]: edb-pge-18.service: Failed with result 'exit-code'.
Feb 09 08:05:45 postgres-tde systemd[1]: Failed to start Postgres Extended 18 database server.
There are two options to resolve this: Either provide the password by passing the reference to a file containing it (not recommended, of course) or you need to use systemd-ask-password and tweak the systemd service definition once more. We’ll be going the easy (and not recommended) way and will provide the password using a file and re-initialize the cluster:
[postgres@postgres-tde ~]$ echo "admin123" > /home/postgres/pass.bin
[postgres@postgres-tde ~]$ /usr/edb/pge18/bin/initdb --data-encryption --key-wrap-command='openssl enc -e -aes-128-cbc -pbkdf2 -pass file:/home/postgres/pass.bin -out "%p"' --key-unwrap-command='openssl enc -d -aes-128-cbc -pbkdf2 -pass file:/home/postgres/pass.bin -in "%p"' --pgdata=/u02/pgdata/pge18/
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are enabled.
Transparent data encryption is enabled (128 bits).
fixing permissions on existing directory /u02/pgdata/pge18 ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Europe/Berlin
creating configuration files ... ok
setting up data encryption ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
Success. You can now start the database server using:
/usr/edb/pge18/bin/pg_ctl -D /u02/pgdata/pge18/ -l logfile start
[postgres@postgres-tde ~]$ grep -i wrap /u02/pgdata/pge18/*.conf
/u02/pgdata/pge18/postgresql.conf:data_encryption_key_unwrap_command = 'openssl enc -d -aes-128-cbc -pbkdf2 -pass file:/home/postgres/pass.bin -in "%p"'
Time to start the instance:
[postgres@postgres-tde ~]$ sudo systemctl start edb-pge-18.service
[postgres@postgres-tde ~]$ sudo systemctl status edb-pge-18.service
● edb-pge-18.service - Postgres Extended 18 database server
Loaded: loaded (/usr/lib/systemd/system/edb-pge-18.service; enabled; preset: disabled)
Drop-In: /etc/systemd/system/edb-pge-18.service.d
└─override.conf
Active: active (running) since Fri 2026-02-13 08:25:39 CET; 4s ago
Docs: https://www.postgresql.org/docs/18/static/
Process: 1691 ExecStartPre=/usr/edb/pge18/bin/edb-pge-18-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
Main PID: 1696 (postgres)
Tasks: 9 (limit: 7834)
Memory: 21.7M (peak: 22.0M)
CPU: 42ms
CGroup: /system.slice/edb-pge-18.service
├─1696 /usr/edb/pge18/bin/postgres -D /u02/pgdata/pge18/
├─1698 "postgres: io worker 0"
├─1699 "postgres: io worker 2"
├─1700 "postgres: io worker 1"
├─1701 "postgres: checkpointer "
├─1702 "postgres: background writer "
├─1704 "postgres: walwriter "
├─1705 "postgres: autovacuum launcher "
└─1706 "postgres: logical replication launcher "
Feb 13 08:25:39 postgres-tde systemd[1]: Starting Postgres Extended 18 database server...
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.774 CET [1696] LOG: starting PostgreSQL 18.1 (EDB Postgres Extended Server 18.1.0) on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5), 64-b>
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.775 CET [1696] LOG: listening on IPv6 address "::1", port 5432
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.775 CET [1696] LOG: listening on IPv4 address "127.0.0.1", port 5432
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.778 CET [1696] LOG: listening on Unix socket "/var/run/edb-pge/.s.PGSQL.5432"
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.781 CET [1696] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
Feb 13 08:25:39 postgres-tde postgres[1703]: 2026-02-13 08:25:39.785 CET [1703] LOG: database system was shut down at 2026-02-13 08:24:53 CET
Feb 13 08:25:39 postgres-tde postgres[1696]: 2026-02-13 08:25:39.789 CET [1696] LOG: database system is ready to accept connections
Feb 13 08:25:39 postgres-tde systemd[1]: Started Postgres Extended 18 database server.
As with Fujitsu, be aware that dumping data with pg_dump (or pg_dumpall) will result in unencrypted data:
[postgres@postgres-tde ~]$ psql
psql (18.1 (EDB Postgres Extended Server 18.1.0))
Type "help" for help.
postgres=# create table t ( a int, b text );
CREATE TABLE
postgres=# insert into t select i, i::text from generate_series(1,100) i;
INSERT 0 100
postgres=# \q
[postgres@postgres-tde ~]$ pg_dump > a.sql
[postgres@postgres-tde ~]$ cat a.sql
--
-- PostgreSQL database dump
--
...
COPY public.t (a, b) FROM stdin;
1 1
2 2
3 3
4 4
...
The tools which require the unwrap command are documented here (initdb, pg_rewind, …).
To check if a cluster has TDE enabled (or was initialized with TDE) you can use the pg_control_init function, which also was modified for TDE purposes:
postgres=# select data_encryption_version, data_encryption_keylen from pg_control_init();
data_encryption_version | data_encryption_keylen
-------------------------+------------------------
1 | 128
(1 row)
Some final thoughts:
- In contracts to Fujitsu’s implementation the EDB implementation is not based on tablespaces but the complete cluster.
- You need to provide the key when the instance is started, otherwise startup will fail. This is not the case with Fujitsu.
- Because encryption is applied instance wide several commands (e.g. pg_rewind) need to be aware of the unwrap command.