TimescaleDB 2 Community on Ubuntu 22.04 on Azure User Guide
Overview
This image runs TimescaleDB 2 Community as a single node time series database on Ubuntu 22.04 LTS. TimescaleDB is a PostgreSQL extension, not a separate engine — the image ships PostgreSQL 16 with the TimescaleDB extension pre installed and enabled. Every PostgreSQL tool and client you already use works unchanged: psql, pg_dump, pg_restore, pgAdmin, DBeaver, every ORM, every driver. Standard SQL plus TimescaleDB's purpose built time series extensions on top.
The differentiator against purpose built time series engines like InfluxDB is compatibility. Teams that already know SQL and already run PostgreSQL in production get native time series storage with hypertables for automatic time based partitioning, continuous aggregates for real time rollups, native columnar compression for cold chunks, and a rich set of time bucket aggregation functions, all accessible with the standard PostgreSQL client protocol on the standard PostgreSQL port 5432. You do not learn a new query language. Your existing backup, monitoring, and connection pooling tooling works without modification.
This image ships the Apache 2.0 licensed Community edition of TimescaleDB, not the Timescale License (TSL) edition. The Community edition includes hypertables, continuous aggregates, native compression, and data retention policies — the core time series capabilities. The TSL edition adds features like data tiering and role level security, and carries cloud hosting restrictions that make it inappropriate for a Marketplace virtual machine image. If your workload requires TSL features, plan a migration to Timescale's hosted service.
Authentication is enforced with a postgres password. On the very first boot of every deployed virtual machine, a unique password is generated for the postgres superuser, and the password is written to a single file readable only by root. Two virtual machines launched from the same gallery image never share credentials. You authenticate locally with psql as the postgres OS user via peer authentication, or remotely with the password over scram-sha-256.
The PostgreSQL listener binds on port 5432, the standard PostgreSQL port. TimescaleDB does not add any new ports. Writes use standard SQL INSERT statements against hypertables. Queries use standard SQL, optionally with TimescaleDB specific functions like time_bucket, time_bucket_gapfill, and locf. There is no embedded web UI; pgAdmin, DBeaver, Grafana, or any PostgreSQL client of your choice works as a visualisation frontend.
The image is intended for teams that want a production posture single node time series store on day one, with full PostgreSQL compatibility, without spending hours on packaging, systemd plumbing, password bootstrap, or extension installation. It is not a replicated cluster, it is not TLS encrypted out of the box, and it does not ship with the TSL edition. Section 19 covers TLS termination and the security posture you want before putting customer traffic through the server.
The brand is lowercase cloudimg throughout this guide. All cloudimg URLs in this guide use the form https://www.cloudimg.co.uk.
Prerequisites
Before you deploy this image you need:
- A Microsoft Azure subscription where you can create resource groups, virtual networks, and virtual machines
- Azure role permissions equivalent to Contributor on the target resource group
- An SSH public key for first login to the admin user account
- A virtual network and subnet in the same region as the Azure Compute Gallery the image is published into, with an associated network security group
- The Azure CLI (
azversion 2.50 or later) installed locally if you intend to use the CLI deployment path in Section 2 - The cloudimg TimescaleDB 2 offer enabled on your tenant in Azure Marketplace
Step 1: Deploy the Virtual Machine from the Azure Portal
Navigate to Marketplace in the Azure Portal, search for TimescaleDB 2, and select the cloudimg publisher entry. Click Create to begin the wizard.
On the Basics tab choose your subscription, target resource group, and region. The region must match the region your Azure Compute Gallery exposes the image in. Set the virtual machine name. Choose SSH public key as the authentication type, set the username to a name of your choice, and paste your SSH public key. Standard_D2s_v3 is the recommended starting size. PostgreSQL with TimescaleDB benefits substantially from memory for the shared_buffers pool and for query caching, and timescaledb-tune sizes shared_buffers and effective_cache_size proportionally to available RAM, so the 8 gigabyte Standard_D2s_v3 profile covers most single node time series workloads comfortably. Scale up to Standard_D4s_v3 or Standard_D8s_v3 for heavier write rates, larger retention windows, or workloads that need concurrent analytical queries.
On the Disks tab the recommended OS disk type is Standard SSD. The PostgreSQL data directory lives at /var/lib/postgresql/16/main. If you expect to ingest more than a few gigabytes per day, attach a separate Premium SSD data disk now and follow Section 18 after the server is running to move the cluster across.
On the Networking tab select your existing virtual network and subnet. Attach a network security group that allows inbound TCP 22 from your management IP range and inbound TCP 5432 only from the virtual network CIDR or the specific application server and Grafana subnets that need to talk to the server. Do not expose 5432 to the public internet. The postgres password plus scram-sha-256 is strong authentication, but an exposed database is an indefinite compromise target and there is no benefit to an open listener.
On the Management, Monitoring, and Advanced tabs the defaults are appropriate. Click Review + create, wait for validation to pass, then click Create. Deployment takes around two minutes.
Step 2: Deploy the Virtual Machine from the Azure CLI
If you prefer the command line, use the gallery image resource identifier as the source. The exact resource identifier is published on your Partner Center plan. A representative invocation:
RG="timescaledb-prod"
LOCATION="eastus"
VM_NAME="timescaledb-01"
ADMIN_USER="dbadmin"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/timescaledb-2-ubuntu-22-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create \
--resource-group "$RG" \
--name timescaledb-vnet \
--address-prefix 10.50.0.0/16 \
--subnet-name timescaledb-subnet \
--subnet-prefix 10.50.1.0/24
az network nsg create --resource-group "$RG" --name timescaledb-nsg
az network nsg rule create \
--resource-group "$RG" --nsg-name timescaledb-nsg \
--name allow-ssh-mgmt --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" \
--destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create \
--resource-group "$RG" --nsg-name timescaledb-nsg \
--name allow-postgres-vnet --priority 110 \
--source-address-prefixes 10.50.0.0/16 \
--destination-port-ranges 5432 --access Allow --protocol Tcp
az vm create \
--resource-group "$RG" \
--name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_D2s_v3 \
--storage-sku StandardSSD_LRS \
--admin-username "$ADMIN_USER" \
--ssh-key-values "$SSH_KEY" \
--vnet-name timescaledb-vnet --subnet timescaledb-subnet \
--nsg timescaledb-nsg \
--public-ip-address "" \
--os-disk-size-gb 32
The --public-ip-address "" flag keeps the server off the public internet. Use a bastion host or your existing private connectivity to reach it.
Step 3: Connect via SSH
After deployment, find the private IP of the new virtual machine. From a host inside the same virtual network:
ssh dbadmin@<vm-ip>
The first login may take a few seconds while cloud init finalises. Once you have a shell, PostgreSQL has already been started by systemd and the first boot oneshot has already rotated the postgres password into a virtual machine unique value.
Step 4: Retrieve the Postgres Password
The postgres password is written by the timescaledb-firstboot.service systemd oneshot the very first time the virtual machine boots. It lives in a single file, readable only by root:
sudo awk -F= '/^POSTGRES_PASSWORD=/{print $2}' /stage/scripts/timescaledb-credentials.log
You can also sudo cat the full file to see the adjacent fields:
sudo cat /stage/scripts/timescaledb-credentials.log
The file contains lines like:
port=5432
default_database=tsdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=Vn3x2PpBq5wYcE7sTrL9jHm4
sample_connect=PGPASSWORD='Vn3x2PpBq5wYcE7sTrL9jHm4' psql -h localhost -U postgres -d tsdb
Every customer virtual machine deployed from this image has a different password. Treat it like a secret: paste it into your secrets manager, do not check it into version control, and rotate it before production as described in Section 17.
Step 5: Server Components
| Component | Version | Purpose |
|---|---|---|
| PostgreSQL | 16.x (pinned at build time, printed by SELECT version()) |
SQL database engine with write ahead logging, MVCC, scram-sha-256 authentication |
| TimescaleDB extension | 2.x.x Community edition, Apache 2.0 (printed by SELECT extversion FROM pg_extension WHERE extname='timescaledb') |
Time series extension: hypertables, continuous aggregates, native compression, time bucket aggregates |
| Ubuntu | 22.04 LTS | Base operating system |
| systemd units | postgresql.service, timescaledb-firstboot.service | Process supervision and first boot password rotation |
PostgreSQL is installed from the official PostgreSQL Global Development Group (PGDG) APT repository at apt.postgresql.org. TimescaleDB is installed from the Timescale packagecloud repository at packagecloud.io/timescale/timescaledb. Both repositories stay configured on the VM so apt-get update && apt-get upgrade picks up future security patches. The firstboot oneshot runs with Type=oneshot and RemainAfterExit=yes, gated by the sentinel file /var/lib/postgresql/16/main/.firstboot-done, so it runs exactly once per virtual machine.
Step 6: Filesystem Layout
| Path | Owner | Purpose |
|---|---|---|
| /usr/lib/postgresql/16/bin/ | root:root 0755 | PostgreSQL 16 binaries (postgres, pg_ctl, pg_basebackup, etc.) |
| /usr/lib/postgresql/16/lib/timescaledb-*.so | root:root 0644 | Compiled TimescaleDB extension shared library |
| /etc/postgresql/16/main/postgresql.conf | postgres:postgres 0644 | PostgreSQL server configuration (listen_addresses, shared_preload_libraries, memory tuning from timescaledb-tune) |
| /etc/postgresql/16/main/pg_hba.conf | postgres:postgres 0640 | Host based authentication rules (scram-sha-256 for remote, peer for local postgres) |
| /etc/systemd/system/timescaledb-firstboot.service | root:root 0644 | Oneshot that rotates the postgres password on first boot |
| /usr/local/sbin/timescaledb-firstboot.sh | root:root 0750 | Firstboot script (invoked by the oneshot) |
| /usr/local/sbin/timescaledb-start.sh, timescaledb-stop.sh, setEnv.sh | root:root 0755 | Convenience wrappers around systemctl |
| /var/lib/postgresql/16/main/ | postgres:postgres 0700 | PostgreSQL cluster data directory: write ahead log, heap files, indexes, toast |
| /var/lib/postgresql/16/main/.firstboot-done | postgres:postgres 0644 | Sentinel: presence means firstboot has already run |
| /stage/scripts/timescaledb-credentials.log | root:root 0600 | Generated postgres password, readable by root only |
PostgreSQL logs go to the systemd journal by default; view them with journalctl -u postgresql -f. Debian's PostgreSQL packaging uses the postgresql@16-main.service template unit instance for the cluster and postgresql.service as the meta unit that starts all enabled clusters — either name works for systemctl control.
Step 7: Start, Stop, and Check Status
Check the service status:
sudo systemctl status postgresql --no-pager
sudo systemctl is-active postgresql
Start the service (idempotent if it is already running):
sudo systemctl start postgresql
To stop or restart use the corresponding systemctl verbs: sudo systemctl stop postgresql, sudo systemctl restart postgresql. The cloudimg helper scripts at /usr/local/sbin/timescaledb-start.sh and /usr/local/sbin/timescaledb-stop.sh wrap these for convenience.
Tail the server logs via the journal:
sudo journalctl -u postgresql -n 50 --no-pager
The firstboot oneshot is separate. It runs exactly once on the customer's first boot and should show as active (exited) from then on:
sudo systemctl status timescaledb-firstboot --no-pager
Step 8: Verify the Server is Healthy
The PostgreSQL pg_isready utility returns exit code 0 when the server is accepting connections. It is the canonical health check:
pg_isready -h localhost -p 5432
Expected output is /var/run/postgresql:5432 - accepting connections with exit code 0. A non zero exit code means the server is not ready: check sudo systemctl status postgresql and sudo journalctl -u postgresql -n 50 --no-pager for detail.
Step 9: Connect Locally
Connect as the postgres OS user via peer authentication, which requires no password:
sudo -u postgres psql -d tsdb -c "SELECT 1 AS ok;"
Or check the server version:
sudo -u postgres psql -d tsdb -c "SELECT version();"
You can also open an interactive psql shell by running sudo -u postgres psql -d tsdb without a -c flag. Type \q to exit. Standard psql meta commands work: \l lists databases, \dt lists tables, \d <table> describes a table's schema, \dx lists installed extensions.
Step 10: Verify the TimescaleDB Extension
The TimescaleDB extension is pre installed and enabled in the default tsdb database. Confirm it loaded by querying pg_extension:
sudo -u postgres psql -d tsdb -c "SELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';"
Expected output is a single row with extname = timescaledb and an extversion like 2.15.0 or similar. An empty result means the extension is not installed — check sudo journalctl -u postgresql -n 50 --no-pager for the reason.
Verify the shared_preload_libraries postgresql.conf setting contains timescaledb. Without this, the extension cannot load and any CREATE EXTENSION timescaledb call returns a "could not access file" error:
sudo -u postgres psql -d tsdb -c "SHOW shared_preload_libraries;"
Expected output contains timescaledb (it may be the only entry, or it may be in a comma separated list with other extensions).
Step 11: Create a Hypertable
The core TimescaleDB primitive is the hypertable: a regular PostgreSQL table that is automatically partitioned by a time column into chunks. Chunks are created on demand as data is inserted, and queries are transparently routed across the right chunks by the TimescaleDB planner hook.
Create a demonstration hypertable for sensor metrics:
sudo -u postgres psql -d tsdb -c "CREATE TABLE IF NOT EXISTS demo_metrics (time TIMESTAMPTZ NOT NULL, sensor_id INT NOT NULL, temperature DOUBLE PRECISION, humidity DOUBLE PRECISION);"
Convert the regular table into a hypertable:
sudo -u postgres psql -d tsdb -c "SELECT create_hypertable('demo_metrics', by_range('time'), if_not_exists => TRUE);"
Expected output is a single row like (1,public,demo_metrics,t) indicating a new hypertable was created. by_range('time') tells TimescaleDB to partition by the time column with the default 7 day chunk interval. if_not_exists => TRUE makes the call idempotent.
Step 12: Insert Data and Run a time_bucket Aggregate
Insert sample metric rows:
sudo -u postgres psql -d tsdb -c "INSERT INTO demo_metrics VALUES (NOW(), 1, 23.5, 65.2), (NOW() - INTERVAL '1 hour', 1, 22.8, 67.1), (NOW() - INTERVAL '2 hours', 2, 24.1, 60.3);"
Expected output is INSERT 0 3, meaning three rows inserted.
Run a time_bucket aggregate to compute hourly averages per sensor:
sudo -u postgres psql -d tsdb -c "SELECT time_bucket('1 hour', time) AS bucket, sensor_id, AVG(temperature) FROM demo_metrics GROUP BY bucket, sensor_id ORDER BY bucket;"
Expected output is three rows (one per hour bucket per sensor), showing bucket | sensor_id | avg columns. time_bucket is TimescaleDB's equivalent of PostgreSQL's date_trunc but works with arbitrary bucket sizes (minute, 5 minute, hour, day, anything).
Inspect the hypertable's chunks:
sudo -u postgres psql -d tsdb -c "SELECT show_chunks('demo_metrics');"
Expected output is one row showing a chunk name like _timescaledb_internal._hyper_1_1_chunk.
When you are done with the demonstration, drop the table to clean up:
sudo -u postgres psql -d tsdb -c "DROP TABLE demo_metrics;"
Step 13: Connect Remotely
From your application server or workstation inside the same virtual network:
PGPASSWORD='<your-password>' psql -h <vm-ip> -U postgres -d tsdb
Or set the password once for the session:
export PGPASSWORD='<your-password>'
psql -h <vm-ip> -U postgres -d tsdb
The remote listener is bound to 0.0.0.0:5432 and pg_hba.conf allows scram-sha-256 authentication from any source IP; the NSG is the effective firewall. Restrict inbound 5432 to your application subnets.
Step 14: Connect from Applications
Every PostgreSQL driver speaks to TimescaleDB unchanged because TimescaleDB is a PostgreSQL extension. The connection string is standard libpq format:
postgresql://postgres:<your-password>@<vm-ip>:5432/tsdb
A representative Python example using psycopg2:
import psycopg2
conn = psycopg2.connect(
host="<vm-ip>",
port=5432,
user="postgres",
password="<your-password>",
dbname="tsdb",
)
cur = conn.cursor()
cur.execute("SELECT time_bucket('5 minutes', time) AS b, avg(temperature) FROM metrics WHERE time > now() - interval '1 hour' GROUP BY b ORDER BY b;")
for row in cur.fetchall():
print(row)
A representative Node.js example using pg:
const { Client } = require("pg");
const client = new Client({
host: "<vm-ip>",
port: 5432,
user: "postgres",
password: "<your-password>",
database: "tsdb",
});
await client.connect();
const r = await client.query(
"SELECT time_bucket('5 minutes', time) AS b, avg(temperature) FROM metrics WHERE time > now() - interval '1 hour' GROUP BY b ORDER BY b;",
);
console.log(r.rows);
Java (JDBC) uses the standard org.postgresql:postgresql driver with a connection URL of jdbc:postgresql://<vm-ip>:5432/tsdb. Go uses lib/pq or pgx. Rust uses tokio-postgres. Every ORM that speaks PostgreSQL works: SQLAlchemy, Prisma, Hibernate, GORM, Diesel, TypeORM.
Step 15: Compression for Cold Chunks
TimescaleDB Community ships native columnar compression. Enable it on a hypertable and compress chunks that are older than a threshold to reclaim disk space.
ALTER TABLE metrics SET (timescaledb.compress, timescaledb.compress_segmentby = 'sensor_id');
SELECT add_compression_policy('metrics', INTERVAL '7 days');
After the policy is in place, chunks older than 7 days compress automatically on the background worker. You can also compress manually:
SELECT compress_chunk(c) FROM show_chunks('metrics', older_than => INTERVAL '7 days') c;
Compression typically yields 90% or better space savings on time series data, at the cost of slower point inserts into compressed chunks. Set your chunk interval appropriately so newly arriving data writes to uncompressed chunks.
Step 16: Continuous Aggregates for Real Time Rollups
Continuous aggregates are materialised views that track a hypertable and refresh incrementally. Queries hit the pre aggregated view; writes still go to the underlying hypertable.
CREATE MATERIALIZED VIEW metrics_5m
WITH (timescaledb.continuous) AS
SELECT time_bucket('5 minutes', time) AS b,
sensor_id,
AVG(temperature) AS avg_temp,
MAX(temperature) AS max_temp
FROM metrics
GROUP BY b, sensor_id;
SELECT add_continuous_aggregate_policy('metrics_5m',
start_offset => INTERVAL '1 hour',
end_offset => INTERVAL '5 minutes',
schedule_interval => INTERVAL '5 minutes');
After creation, the view is usable like any table: SELECT * FROM metrics_5m WHERE b > now() - interval '1 day';. The refresh policy keeps it up to date.
Step 17: Rotate the Postgres Password
The firstboot generated password should be rotated before production. The rotation pattern is: generate a new random password, apply it with ALTER USER, update the credentials file, then update every client.
NEW_PASSWORD="$(openssl rand -base64 24 | tr -d '=+/' | cut -c1-24)"
sudo -u postgres psql -c "ALTER USER postgres PASSWORD '$NEW_PASSWORD';"
sudo sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$NEW_PASSWORD|" /stage/scripts/timescaledb-credentials.log
sudo sed -i "s|PGPASSWORD='[^']*'|PGPASSWORD='$NEW_PASSWORD'|" /stage/scripts/timescaledb-credentials.log
Then update every application that connects with the old password. There is no automatic rotation; put it on your security calendar.
For application access, create dedicated PostgreSQL users with minimum required privileges rather than handing the postgres superuser credentials to every application:
CREATE USER app_writer WITH PASSWORD '<app-password>';
GRANT CONNECT ON DATABASE tsdb TO app_writer;
GRANT USAGE ON SCHEMA public TO app_writer;
GRANT SELECT, INSERT ON ALL TABLES IN SCHEMA public TO app_writer;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT ON TABLES TO app_writer;
Rotate application passwords on the same cadence as the postgres superuser password.
Step 18: Move Data to an Attached Premium Disk
For production workloads larger than the OS disk can comfortably hold, attach a Premium SSD data disk and move the PostgreSQL data directory across.
# 1. Identify the new block device (look for the unpartitioned disk)
lsblk
# 2. Format and mount it
sudo mkfs.ext4 /dev/sdc
sudo mkdir -p /data
sudo mount /dev/sdc /data
sudo blkid /dev/sdc # copy the UUID into /etc/fstab for persistence
# 3. Stop PostgreSQL and move the data directory
sudo systemctl stop postgresql
sudo rsync -aAX /var/lib/postgresql/16/main/ /data/postgresql-16-main/
sudo chown -R postgres:postgres /data/postgresql-16-main
sudo mv /var/lib/postgresql/16/main /var/lib/postgresql/16/main.bak
# 4. Point the cluster at the new location
sudo pg_conftool 16 main set data_directory '/data/postgresql-16-main'
# 5. Restart and verify
sudo systemctl start postgresql
sudo journalctl -u postgresql -f
Only after the server has started cleanly and your queries work should you delete /var/lib/postgresql/16/main.bak.
Step 19: Troubleshooting
Cannot connect on 5432 from another host
Verify the server is bound to 0.0.0.0 rather than 127.0.0.1:
sudo ss -tlnp | grep 5432
If only 127.0.0.1 is shown, check that /etc/postgresql/16/main/postgresql.conf has listen_addresses = '*' and restart. If the server is bound correctly, check /etc/postgresql/16/main/pg_hba.conf has the host all all 0.0.0.0/0 scram-sha-256 line. If both are fine, the problem is your Azure NSG; confirm inbound TCP 5432 is allowed from the caller's CIDR.
Authentication fails with "password authentication failed"
The wrong password is being used. Re read /stage/scripts/timescaledb-credentials.log to confirm the current password, and be sure the PGPASSWORD environment variable or -W prompt is supplying the exact value. Passwords are opaque strings; copying only part of one is a common mistake.
CREATE EXTENSION timescaledb fails with "could not access file"
The shared_preload_libraries setting does not include timescaledb. Check:
sudo -u postgres psql -d tsdb -c "SHOW shared_preload_libraries;"
If timescaledb is missing, edit /etc/postgresql/16/main/postgresql.conf, set shared_preload_libraries = 'timescaledb', and restart with sudo systemctl restart postgresql. The extension library is only loaded at server start, so a restart (not a reload) is required.
Queries are slow on a large hypertable
Run EXPLAIN ANALYZE <query> and look at the chunk access pattern. Common fixes: tighten the time range filter (TimescaleDB's chunk exclusion depends on a predicate on the partitioning column), add indexes on frequently filtered columns (a standard CREATE INDEX applies to all chunks of a hypertable), enable compression on cold chunks (Section 15), and verify chunk_time_interval is appropriate for your data rate — too small creates metadata overhead, too large defeats chunk exclusion.
Disk usage climbs faster than expected
Write ahead log rotation is automatic but compaction (via compression) is not. Enable a compression policy on the hypertable (Section 15) so old chunks shrink to a fraction of their uncompressed size. Also check sudo du -sh /var/lib/postgresql/16/main/pg_wal to see if WAL is accumulating beyond the configured retention; if so, consider increasing max_wal_size or ensuring archival is consuming WAL.
PostgreSQL refuses to start after a config change
Run sudo systemctl status postgresql and look at the last journal lines. A common cause is a syntax error in postgresql.conf or pg_hba.conf; fix the file and sudo systemctl restart postgresql. You can validate postgresql.conf ahead of a restart with sudo -u postgres /usr/lib/postgresql/16/bin/postgres --config-file=/etc/postgresql/16/main/postgresql.conf -C shared_preload_libraries.
Step 20: Security Recommendations
Change the postgres password before production. The firstboot generated password is a strong random string, but rotation on your schedule is standard hygiene. Section 17 has the commands.
Create application specific users with least privileges. Never give applications the postgres superuser credentials. Create a dedicated role per application with just the CONNECT, USAGE, and minimal table grants they need. Revoke superuser from operational accounts.
Restrict port 5432 in the NSG to your application subnets. The public internet has no business reaching a database. Use Azure Bastion or a jumpbox for administrative access over SSH and psql.
Terminate TLS for remote connections. PostgreSQL supports SSL out of the box; enable it by setting ssl = on in postgresql.conf and placing a certificate and key at ssl_cert_file and ssl_key_file. Clients connect with sslmode=require or sslmode=verify-full. For public Certificate Authority signed certificates, use Let's Encrypt with certbot and symlink the files into place.
Use scram-sha-256 authentication. PostgreSQL 16 defaults to scram-sha-256, which is what the default pg_hba.conf in this image specifies. Do not regress to md5 or trust authentication on any remote rule.
Rotate passwords periodically. There is no automatic rotation; put it on your security calendar.
Keep the system patched. The PGDG and Timescale APT repos stay configured; sudo apt-get update && sudo apt-get upgrade regularly to pick up PostgreSQL minor versions and TimescaleDB security updates. PostgreSQL minor upgrades within a major version are binary compatible and do not require a pg_upgrade.
Step 21: Support and Licensing
cloudimg provides 24/7/365 expert technical support for this image. Guaranteed response within 24 hours; one hour average for critical issues. Contact support@cloudimg.co.uk.
TimescaleDB Community edition is licensed under the Apache 2.0 licence by Timescale, Inc. PostgreSQL is licensed under the PostgreSQL Licence. This image is a repackaged upstream distribution provided by cloudimg; additional charges apply for the pre configured image, ongoing maintenance, and the 24/7 support contract.
Visit www.cloudimg.co.uk/guides/timescaledb-2-on-ubuntu-22-04-azure for the published version of this guide.
TimescaleDB is a trademark of Timescale, Inc. This image uses the Apache 2.0 licensed Community edition. PostgreSQL is a registered trademark of the PostgreSQL Global Development Group. This image is a repackaged upstream distribution provided by cloudimg. Additional charges apply for build, maintenance, and 24/7 support.