Deployment guide

THREDDS Data Server

Overview

The THREDDS Data Server (TDS) is the reference server from NSF Unidata for publishing scientific data. Point it at a directory of netCDF, HDF, GRIB or NEXRAD files and it serves them through the remote access protocols the earth science community already uses: OPeNDAP and DAP4 for subsetting from Python, R, MATLAB, Panoply or the netCDF C library; OGC WMS for map layers; OGC WCS for coverage retrieval; the NetCDF Subset Service for pulling a bounding box or time range out of a large grid; and plain HTTP for whole-file download.

The THREDDS catalog, an XML description of the holdings, lets clients discover what is available and how to reach it. You can write catalogs by hand or generate them by scanning a directory, and the server can aggregate many files into one logical dataset with a continuous time axis.

This image runs THREDDS Data Server 5.9 from the official Unidata release on Apache Tomcat 10.1 under OpenJDK 17, both from Ubuntu's package archive so they keep receiving security updates on your VM. TDS 5.9 is a Jakarta Servlet application compiled to Java 17 bytecode, so it requires Tomcat 10 or later and does not load on Tomcat 9. Java 17 is the runtime NSF Unidata's own continuous integration tests it against.

The catalog works the moment the VM boots, with no external service involved. Nine small sample netCDF files ship inside the server's own distribution and this image publishes them: a global ocean wind and wave forecast grid, four NCEP Eta model runs on the AWIPS 211 grid, a deliberately awkward polar stereographic grid, and two station time series in the CF discrete sampling geometry convention. Nothing in the shipped catalog points at a third-party server, so the server serves real data on a VM with no internet access.

Every VM generates a unique administrator password and a unique TLS certificate on first boot. The catalog and data endpoints are public by design. The management surface is not: /thredds/admin/ is refused over plain HTTP and is reachable only over HTTPS, by an account whose password exists on your VM and nowhere else. Backed by 24/7 SecureAnyCloud support.

What you get:

  • THREDDS Data Server 5.9 on Apache Tomcat 10.1 under OpenJDK 17, both from Ubuntu's archive so security updates keep flowing
  • A working catalog on first boot with nine locally served sample datasets and no dependency on any third-party server
  • OPeNDAP, DAP4, OGC WMS, OGC WCS, the NetCDF Subset Service, CdmRemote and HTTP file download, all enabled
  • Godiva3, the built-in WMS map viewer, opening on the sample data rather than an empty layer tree
  • Per-variable WMS color scales, so a plain GetMap request from QGIS or a Leaflet layer returns a readable map instead of a flat rectangle
  • Tomcat bound to the loopback interface only, behind an nginx reverse proxy that forwards an explicit allowlist and answers everything else with a 404
  • The management surface refused over plain HTTP, and protected by a per-VM administrator account over HTTPS
  • No Tomcat manager or host-manager webapp in the image, and no users or roles in tomcat-users.xml until first boot writes yours
  • Remote catalog fetching, remote WMS and remote WCS all disabled, so the server never fetches a caller-supplied URL
  • The version phone-home switched off, so the server makes no unsolicited outbound call
  • A JVM heap sized from the VM's own memory on every first boot, so one image is correct on any VM size
  • Ubuntu 24.04 LTS fully patched at capture, with unattended security upgrades enabled

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair for the azureuser account
  • A network security group that allows inbound TCP 22, 80 and 443 from the addresses you will use

Step 1 - Deploy from the Azure Marketplace

In the Azure portal, search the Marketplace for THREDDS Data Server 5.9 on Ubuntu 24.04 LTS by SecureAnyCloud, select Create, and choose a size of Standard_B2s or larger.

Set the administrator username to azureuser and supply your SSH public key. On the Networking tab, allow inbound SSH (22), HTTP (80) and HTTPS (443).

Step 2 - Deploy from the Azure CLI

Run these commands to create a resource group, accept the image terms, create the VM and open the ports:

az group create --name thredds-rg --location eastus
az vm image terms accept --urn sac1647283583153:thredds-data-server:default:latest
az vm create \
  --resource-group thredds-rg \
  --name thredds-server \
  --image sac1647283583153:thredds-data-server:default:latest \
  --size Standard_B2s \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group thredds-rg --name thredds-server --port 22 --priority 1001
az vm open-port --resource-group thredds-rg --name thredds-server --port 80 --priority 1002
az vm open-port --resource-group thredds-rg --name thredds-server --port 443 --priority 1003

First boot takes about a minute. A service generates this VM's administrator password and TLS certificate, sizes the JVM heap from the VM's memory, and only then lets Tomcat and nginx start.

Step 3 - Connect to your VM

Connect over SSH as azureuser:

ssh azureuser@<vm-ip>

Step 4 - Confirm the services are running

Check the three services that make up the server:

for unit in thredds-firstboot tomcat10 nginx; do printf '%-26s %s\n' "$unit" "$(systemctl is-active "$unit")" done

tomcat10 and nginx report active. thredds-firstboot is a one-shot unit that reports active once it has done its work, because it is declared RemainAfterExit=yes. That is how systemd remembers that this VM has already been configured. For the full status, run:

systemctl --no-pager --lines=0 status tomcat10 nginx

The two application services wait until first boot has finished. Each has a systemd drop-in with ConditionPathExists on a marker file that first boot writes as its last action. The server therefore never runs before this VM's own credentials exist. Verify the drop-in with:

systemctl cat tomcat10.service | grep -E 'ConditionPathExists|After=thredds'

Step 5 - Confirm the server is answering

Request the catalog and the two probe endpoints:

curl -s -o /dev/null -w 'catalogue: HTTP %{http_code}\n' http://127.0.0.1/thredds/catalog/catalog.xml
curl -s -o /dev/null -w 'health: HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'readiness: HTTP %{http_code}\n' http://127.0.0.1/readyz

/healthz answers as soon as nginx is up. /readyz is proxied through to TDS itself, so it succeeds only once the server is serving its catalog. Use /readyz for a load balancer probe.

Step 6 - Browse the shipped catalog

List the datasets in the catalog:

curl -s http://127.0.0.1/thredds/catalog/catalog.xml | grep -oE '<dataset name="[^"]+" ID="[^"]+"' | head -12

Every dataset listed resolves to a file already on this VM. The catalog is at /var/lib/thredds/content/thredds/catalog.xml and the data it points at is under /var/lib/thredds/content/thredds/public/testdata/.

Step 7 - Retrieve your administrator password

The management surface is the only non-public part of the server. Its credentials are generated on this VM alone. Read them with:

sudo cat /root/thredds-credentials.txt

The file is 0600 root:root and contains THREDDS_ADMIN_USER, THREDDS_ADMIN_PASSWORD, the URL of the administration pages and the SHA-256 fingerprint of this VM's TLS certificate. Tomcat stores the password as a salted, 200,000-iteration SHA-512 digest. The plaintext exists only in this file.

Step 8 - Confirm the management surface is closed

Run these three checks. Each comment in the output states the expected result:

curl -s -o /dev/null -w 'admin over http: HTTP %{http_code} (403 = refused)\n' http://127.0.0.1/thredds/admin/debug
curl -sk -o /dev/null -w 'admin over https, no creds: HTTP %{http_code} (401 = challenged)\n' https://127.0.0.1/thredds/admin/debug
curl -s -o /dev/null -w 'tomcat manager webapp: HTTP %{http_code} (404 = does not exist)\n' http://127.0.0.1/manager/html

Plain HTTP refuses the management paths instead of redirecting, so an administrator password can never cross the network in the clear. Now authenticate over HTTPS with the password from Step 7:

curl -sk -o /dev/null -w 'admin with credentials: HTTP %{http_code}\n' \
  -u "tdsadmin:<THREDDS_ADMIN_PASSWORD>" https://127.0.0.1/thredds/admin/debug

The certificate is self-signed and was generated on this VM, so its private key exists nowhere else. That is why curl needs -k and why a browser warns on the first visit. Step 15 covers installing your own certificate.

Step 9 - Pull real data over OPeNDAP

Most scientific clients talk to a THREDDS server over OPeNDAP. Request the dataset descriptor first, then a subset of actual values:

curl -s http://127.0.0.1/thredds/dodsC/sac/ocean.nc.dds
curl -s 'http://127.0.0.1/thredds/dodsC/sac/ocean.nc.ascii?wave_hgt_sfc%5B0:1:0%5D%5B30:1:33%5D%5B30:1:33%5D'

The values are significant wave heights in meters from a 4 by 4 corner of the forecast grid. The same URL works from Python:

python3 -c "print('pip install netCDF4, then: netCDF4.Dataset(\"http://<vm-ip>/thredds/dodsC/sac/ocean.nc\")')"

Step 10 - Subset a grid with the NetCDF Subset Service

The NetCDF Subset Service (NCSS) has two request shapes. A grid subset returns a netCDF file:

curl -s -o /tmp/subset.nc \
  'http://127.0.0.1/thredds/ncss/grid/sac/ocean.nc?var=wave_hgt_sfc&horizStride=8&temporal=all&accept=netcdf' ls -l /tmp/subset.nc && head -c 3 /tmp/subset.nc && echo ' <- netCDF classic magic'

A grid-as-point request returns a time series at one location as CSV:

curl -s 'http://127.0.0.1/thredds/ncss/grid/sac/ocean.nc?var=wave_hgt_sfc&latitude=20&longitude=-40&time=all&accept=csv'

Grid subsets are netCDF only. Requesting CSV from a grid subset returns HTTP 400. For text output, use the point shape shown above, or the point service over the station samples:

curl -s 'http://127.0.0.1/thredds/ncss/point/sac/H.2.1.1.nc?var=humidity&accept=csv&temporal=all' | head -8

Step 11 - Render a map with OGC WMS

Request the WMS capabilities, then render a map image:

curl -s 'http://127.0.0.1/thredds/wms/sac/ocean.nc?service=WMS&version=1.3.0&request=GetCapabilities' | grep -oE '<Name>[^<]+</Name>' | head -8
curl -s -o /tmp/wave.png \
  'http://127.0.0.1/thredds/wms/sac/ocean.nc?service=WMS&version=1.3.0&request=GetMap&layers=wave_hgt_sfc&styles=&format=image/png&transparent=false&crs=CRS:84&bbox=-180,-90,180,90&width=512&height=256' file /tmp/wave.png

That request carries no colorscalerange, which is what a GIS client sends by default. This image gives every shipped variable a color scale range matched to its physical range in /var/lib/thredds/content/thredds/wmsConfig.xml, so the default request returns a readable map. Add a block there for your own variables when you publish them. Otherwise the same request returns one flat color.

Step 12 - Download a whole file

Download a file over HTTP and compare its checksum with the file on disk:

curl -s -o /tmp/ocean.nc http://127.0.0.1/thredds/fileServer/sac/ocean.nc sha256sum /tmp/ocean.nc /var/lib/thredds/content/thredds/public/testdata/ocean.nc

The two checksums match, because the HTTP file service returns the bytes on disk untouched.

Step 13 - Open the server in your browser

Browse to http://<vm-ip>/thredds/catalog/catalog.html. The catalog lists every shipped dataset.

Select a dataset to see every access method it offers, with the live URL for each one.

Step 14 - Draw maps in Godiva3 and subset in the browser

Godiva3 is the WMS viewer built into TDS. Browse to http://<vm-ip>/thredds/Godiva.html. It opens on the sample ocean dataset. Expand the dataset in the tree on the left and pick a variable.

Godiva3 draws its background map from a public tile service on the internet. On a VM with no outbound access, the data layer renders over a blank background. The data itself is served entirely from your VM.

The NetCDF Subset Service also has a browser form. Open the NetcdfSubset link on any grid dataset page to pick variables, drag a bounding box and choose an output format.

Step 15 - Make the server your own

Make these four changes when you publish this server.

Identify your organization. TDS renders these details on every generated page and in the metadata clients harvest:

grep -A 12 '<serverInformation>' /var/lib/thredds/content/thredds/threddsConfig.xml

Edit /var/lib/thredds/content/thredds/threddsConfig.xml with your editor, then restart Tomcat:

sudo systemctl restart tomcat10

Install a real TLS certificate. Replace the self-signed pair and reload nginx. First, list the current files:

ls -l /etc/ssl/sac-thredds/

Place your certificate at /etc/ssl/sac-thredds/server.crt and its key at /etc/ssl/sac-thredds/server.key (key mode 0600 root:root), then sudo systemctl reload nginx.

Add a data disk for real collections. The shipped samples live on the OS disk, which is fine for evaluation. For a real archive, attach an Azure data disk, format it, and mount it with a UUID-keyed nofail entry in /etc/fstab. Then point a datasetScan at it.

Publish your own data. Drop files into the scanned directory and they appear in the catalog with every service already attached:

ls /var/lib/thredds/content/thredds/public/testdata/ curl -s http://127.0.0.1/thredds/catalog/sacAll/catalog.xml | grep -c 'urlPath'

For a curated catalog, edit /var/lib/thredds/content/thredds/catalog.xml and add dataset or datasetScan entries. Then reload the configuration from the management pages or restart Tomcat. The full catalog specification is in the THREDDS documentation.

Maintenance

The JVM heap is sized from the VM's memory on first boot. On a Standard_B2s it is 1467 MB, measured under real query load with roughly 2.7 GB still free and no swap in use. If you move to a larger VM size, the heap follows automatically on that VM's first boot. To change it later, edit JAVA_OPTS in /etc/default/tomcat10 and restart Tomcat.

grep -oE '\-Xm[sx][0-9]+m' /etc/default/tomcat10 free -m | head -2

TDS writes its own logs under the content root and rotates them weekly:

sudo ls -l /var/lib/thredds/content/thredds/logs/

Ubuntu security updates are applied automatically. Confirm the setting with:

grep -E 'Unattended-Upgrade|Update-Package-Lists' /etc/apt/apt.conf.d/20auto-upgrades

To upgrade TDS itself, download the newer thredds-<version>.war from Unidata, verify it against the published .sha256, and unpack it over /var/lib/tomcat10/webapps/thredds/. Your configuration lives outside the webapp, under /var/lib/thredds/content/, so the upgrade leaves it untouched.

Support

SecureAnyCloud maintains this image and provides 24/7 support. NSF Unidata releases the THREDDS Data Server under the BSD 3-Clause license. The full text ships at /usr/share/doc/thredds-data-server/LICENSE, alongside a note recording the exact upstream artifact and its checksum.

head -4 /usr/share/doc/thredds-data-server/LICENSE cat /usr/share/doc/thredds-data-server/README.sac

Deploy on Azure

Launch THREDDS Data Server 5.9 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure