Cantaloupe IIIF Image Server 5.0.8 on AWS User Guide
Overview
This guide covers launching and using the Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg media servers listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Cantaloupe is an open source image server for the IIIF Image API, the image delivery standard of the International Image Interoperability Framework used by libraries, museums, archives and digital humanities projects. Each of your images gets a IIIF address. A client asks for any region of it, at any size, rotation, quality and format, and Cantaloupe cuts and scales that derivative on the fly, so a deep zoom viewer only ever downloads the tiles on screen. An info.json document describes each image: its size, the tile sizes and what the server supports.
On this image the IIIF Image API 3 base is https://<host>/iiif/3/ (and API 2 at https://<host>/iiif/2/). Both are served over HTTPS only; port 80 only redirects.
The cloudimg image locks Cantaloupe down:
- A password unique to your instance. nginx puts HTTP Basic Auth (user
admin) in front of every IIIF request. The password is generated on your instance's first boot and written to/root/cantaloupe-credentials.txt(root only). Nothing secret is in the image. - Images from a local directory only. Cantaloupe reads source images from
/var/lib/cantaloupe/imagesand nowhere else. Its HTTP, S3, Azure and database sources and its delegate script are switched off, so no request can make the server fetch a URL, and a path outside the directory is refused. - No network access for Cantaloupe. Cantaloupe listens on
127.0.0.1:8182behind nginx, runs as the unprivilegedcantaloupeaccount in a systemd sandbox, and may not send traffic to any address but the loopback. Neither Cantaloupe nor nginx can reach the EC2 instance metadata service. - Bounded work per request. Upscaling is refused, so no derivative is larger than its source image; a
maxsize request is capped at 10 megapixels; nginx rate-limits each client and onlyGETandHEADare accepted. Error pages carry no stack trace. - Admin console and API off. The control panel, the REST API, the health endpoint and the legacy IIIF 1 endpoint are disabled; nginx answers
404for anything that is not the IIIF API. - HTTPS from the first boot. nginx serves port 443 with a certificate generated on your instance. A certificate you install yourself is never replaced (Step 9).
- JPEG 2000 without proprietary code. JPEG 2000 images are decoded with OpenJPEG from Ubuntu. The upstream release archive's optional Kakadu libraries are not on the image.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Cantaloupe 5.0.8 (University of Illinois/NCSA Open Source License), OpenJDK 21, OpenJPEG and nginx from Ubuntu 24.04, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 22 (SSH) from your own network, and TCP 443 (the IIIF API) and TCP 80 (redirects to HTTPS) from the networks your clients use. Open 443 only as widely as your clients need.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, a security group as above, and a subnet. Your images live on the root volume; raise its size under Configure storage if your collection is large.
With the AWS CLI (replace the values in angle brackets; this sets a 100 GiB root volume):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=100,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=cantaloupe}]'
Step 2: Connecting to your instance
| Version | SSH user |
|---|---|
| Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot works out the instance's name, generates the certificate and the password, starts Cantaloupe, proves a IIIF round trip over HTTPS and only then opens the web server. It takes about a minute:
systemctl is-active cantaloupe nginx cantaloupe-set-host
active
active
active
cantaloupe-set-host runs on every boot before nginx: it keeps the served name, the certificate and nginx's host rules in step (Step 9).
Only SSH (22) and nginx (80, 443) listen on the network. Cantaloupe's own listener is on the loopback address only (Java shows it as the IPv4-mapped [::ffff:127.0.0.1]:8182). The :53 entries are Ubuntu's local DNS resolver, :323 is the time service on the loopback address and :68 is the DHCP client:
sudo ss -lntup | awk 'NR>1 {print $1, $5}' | sort -u
tcp 0.0.0.0:22
tcp 0.0.0.0:443
tcp 0.0.0.0:80
tcp 127.0.0.53%lo:53
tcp 127.0.0.54:53
tcp [::]:22
tcp [::]:443
tcp [::]:80
tcp [::ffff:127.0.0.1]:8182
udp 127.0.0.1:323
udp 127.0.0.53%lo:53
udp 127.0.0.54:53
udp 172.31.16.10%ens5:68
udp [::1]:323
Cantaloupe runs as the cantaloupe account on Ubuntu's OpenJDK 21, with its configuration in /etc/cantaloupe/cantaloupe.properties:
ps -o user:12=,args= -C java
java -version 2>&1 | head -1
cantaloupe /usr/bin/java -XX:MaxRAMPercentage=50 -XX:+ExitOnOutOfMemoryError -XX:-UsePerfData -Djava.awt.headless=true -Djava.io.tmpdir=/var/lib/cantaloupe/tmp -Duser.home=/var/lib/cantaloupe/tmp -Dcantaloupe.config=/etc/cantaloupe/cantaloupe.properties -jar /opt/cantaloupe/cantaloupe-5.0.8.jar
openjdk version "21.0.12.1" 2026-08-18
Cantaloupe may use up to half of the instance's memory for its Java heap, so a larger instance type gives it more room for big images without any change.
If nginx is not running, first boot has not finished, or it failed, in which case the IIIF API is never opened. Check systemctl status cantaloupe-firstboot and sudo tail -40 /var/log/cloudimg/cantaloupe-firstboot.log.
Step 4: Get your IIIF address and password
sudo cat /root/cantaloupe-credentials.txt
The file is readable by root only and holds the service address, this instance's Basic Auth user and password and the certificate fingerprint:
# Cantaloupe IIIF Image Server - generated on first boot. Unique to this instance; keep it safe.
# IIIF Image API 3: CANTALOUPE_IIIF_BASE<file name>/info.json (API 2: replace /iiif/3/ with /iiif/2/), with HTTP Basic
# Auth (CANTALOUPE_USERNAME / CANTALOUPE_PASSWORD). Images are served from /var/lib/cantaloupe/images. Compare
# CANTALOUPE_TLS_SHA256 with the certificate your client sees (or install your own certificate: see the guide).
CANTALOUPE_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/
CANTALOUPE_IIIF_BASE=https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/3/
CANTALOUPE_USERNAME=admin
CANTALOUPE_PASSWORD=<CANTALOUPE_PASSWORD>
CANTALOUPE_TLS_SHA256=3F:76:70:FE:F2:5D:BA:37:A1:D4:BE:C7:50:FE:41:07:55:49:49:09:EE:B1:79:14:65:FB:82:3C:4A:E7:2C:E3
To see everything but the password:
sudo grep -E '^CANTALOUPE_(URL|IIIF_BASE|USERNAME|TLS_SHA256)=' /root/cantaloupe-credentials.txt
CANTALOUPE_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/
CANTALOUPE_IIIF_BASE=https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/3/
CANTALOUPE_USERNAME=admin
CANTALOUPE_TLS_SHA256=3F:76:70:FE:F2:5D:BA:37:A1:D4:BE:C7:50:FE:41:07:55:49:49:09:EE:B1:79:14:65:FB:82:3C:4A:E7:2C:E3
You can confirm the whole path from the instance itself. The helper fetches info.json and renders JPEG, PNG and JPEG 2000 derivatives through nginx over HTTPS with the certificate pinned, and checks that requests without the password, with a wrong password, writes, upscaling, paths outside the images directory and remote URLs are all refused:
sudo /usr/local/sbin/cantaloupe-verify.sh
CANTALOUPE_ROUNDTRIP_OK (IIIF 3 + 2 over HTTPS: info.json ids on the configured host, JPEG + JPEG 2000 renders; unauth, wrong password, writes, upscaling, traversal and remote identifiers refused)
Change the password with cantaloupe-set-password. With no argument it generates a new 24 character password; with - it reads yours from standard input (at least 16 printable characters, no spaces, :, " or backslash). It never accepts the password as an argument, so it does not appear in the process list or your shell history. It updates /root/cantaloupe-credentials.txt and nginx refuses the old password as soon as the command returns:
$ sudo cantaloupe-set-password
cantaloupe-set-password: password replaced (sudo cat /root/cantaloupe-credentials.txt)
To choose your own:
$ read -rs NEWPW && printf '%s\n' "$NEWPW" | sudo cantaloupe-set-password - ; unset NEWPW
cantaloupe-set-password: password replaced (sudo cat /root/cantaloupe-credentials.txt)
Step 5: Request images with the IIIF Image API
The image comes with two demo images in /var/lib/cantaloupe/images: sample.jpg and sample.jp2 (the same 2400 x 1600 picture as JPEG and JPEG 2000). This step uses curl on the instance; from your own machine the requests are the same, with the address from Step 4.
First a private work directory in memory (/dev/shm), a copy of the instance's certificate, and a curl configuration file that holds the password, so the password is never on a command line or in your shell history:
install -d -m 0700 /dev/shm/iiif-demo && cd /dev/shm/iiif-demo
sudo sed -n 's/^CANTALOUPE_IIIF_BASE=//p' /root/cantaloupe-credentials.txt > base
cp /etc/ssl/cantaloupe/cantaloupe.crt server.crt
( umask 077
sudo sed -n 's/^CANTALOUPE_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/cantaloupe-credentials.txt > iiif.cfg
printf 'cacert = "server.crt"\nsilent\nshow-error\n' >> iiif.cfg )
ls -l iiif.cfg | awk '{print $1, $9}'; cat base
-rw------- iiif.cfg
https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/3/
The image information document. An image's IIIF address is the base followed by its file name. info.json gives its size, the ready-made sizes and the tile layout a viewer uses. Its id is always built from the instance's configured name (Step 9), never from what the request claims:
cd /dev/shm/iiif-demo
curl -K iiif.cfg -o info.json -w '%{http_code} %{content_type}\n' "$(cat base)sample.jpg/info.json"
python3 -c 'import json; d = json.load(open("info.json")); print(d["id"]); print(d["type"], d["profile"], d["width"], "x", d["height"]); print("sizes:", [s["width"] for s in d["sizes"]]); print("tiles:", d["tiles"])'
200 application/ld+json;charset=UTF-8;profile="http://iiif.io/api/image/3/context.json"
https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/3/sample.jpg
ImageService3 level2 2400 x 1600
sizes: [150, 300, 600, 1200, 2400]
tiles: [{'width': 512, 'height': 512, 'scaleFactors': [1, 2, 4, 8, 16]}]
Derivatives. A IIIF image request is {base}{file}/{region}/{size}/{rotation}/{quality}.{format}. These ask for the whole image 600 pixels wide, the right half at full size as PNG, a 300 x 300 grey square rotated by 90 degrees, and one 512 pixel tile:
cd /dev/shm/iiif-demo
B="$(cat base)sample.jpg"
curl -K iiif.cfg -o full-600.jpg -w '%{http_code} %{content_type}\n' "$B/full/600,/0/default.jpg"
curl -K iiif.cfg -o region.png -w '%{http_code} %{content_type}\n' "$B/1200,0,1200,800/max/0/default.png"
curl -K iiif.cfg -o square-gray.jpg -w '%{http_code} %{content_type}\n' "$B/square/300,300/90/gray.jpg"
curl -K iiif.cfg -o tile.jpg -w '%{http_code} %{content_type}\n' "$B/0,0,1024,1024/512,512/0/default.jpg"
file -b full-600.jpg region.png square-gray.jpg tile.jpg | cut -d, -f1-2,8
200 image/jpeg
200 image/png
200 image/jpeg
200 image/jpeg
JPEG image data, JFIF standard 1.02, 600x400
PNG image data, 1200 x 800
JPEG image data, JFIF standard 1.02, 300x300
JPEG image data, JFIF standard 1.02, 512x512
IIIF Image API 2 clients (older viewers and manifests) use the /iiif/2/ base; the document then carries @id and the API 2 profile:
cd /dev/shm/iiif-demo
curl -K iiif.cfg -o info2.json -w '%{http_code}\n' "$(cat base | sed 's#/iiif/3/#/iiif/2/#')sample.jpg/info.json"
python3 -c 'import json; d = json.load(open("info2.json")); print(d["@id"]); print(d["profile"][0], d["width"], "x", d["height"])'
200
https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/2/sample.jpg
http://iiif.io/api/image/2/level2.json 2400 x 1600
JPEG 2000 images are decoded with OpenJPEG (opj_decompress), and are served the same way:
cd /dev/shm/iiif-demo
curl -K iiif.cfg -o jp2.json -w '%{http_code}\n' "$(cat base)sample.jp2/info.json"
python3 -c 'import json; d = json.load(open("jp2.json")); print(d["id"], d["width"], "x", d["height"])'
curl -K iiif.cfg -o jp2-tile.jpg -w '%{http_code} %{content_type}\n' "$(cat base)sample.jp2/0,0,1200,800/600,/0/default.jpg"
200
https://ec2-203-0-113-10.compute-1.amazonaws.com/iiif/3/sample.jp2 2400 x 1600
200 image/jpeg
What is refused. No password, upscaling beyond the original size, a file that is not in the images directory, a remote URL given as the identifier, and any method but GET and HEAD:
cd /dev/shm/iiif-demo
B="$(cat base)sample.jpg"
curl -s --cacert server.crt -o /dev/null -w 'no password: %{http_code}\n' "$B/info.json"
curl -K iiif.cfg -o /dev/null -w 'upscaled (^pct:200): %{http_code}\n' "$B/full/%5Epct:200/0/default.jpg"
curl -K iiif.cfg -o /dev/null -w 'not in the images directory: %{http_code}\n' "$(cat base)no-such-image.jpg/info.json"
curl -K iiif.cfg -o /dev/null -w 'a remote URL as the identifier: %{http_code}\n' "$(cat base)https:%2F%2Fexample.com%2Fa.jpg/info.json"
curl -K iiif.cfg -o /dev/null -w 'POST: %{http_code}\n' -X POST "$B/info.json"
no password: 401
upscaled (^pct:200): 403
not in the images directory: 404
a remote URL as the identifier: 404
POST: 405
Step 6: Add your own images
Copy your images into /var/lib/cantaloupe/images. They must be readable by the cantaloupe account; root-owned files with mode 0644 (directories 0755) are the simplest. Cantaloupe reads JPEG, PNG, TIFF (including tiled and pyramidal TIFF), GIF, BMP and JPEG 2000. An image's identifier is its path below the directory; a file in a subdirectory is addressed with the slash encoded as %2F.
From your own machine, for example:
scp -i <your-key.pem> my-photo.jpg ubuntu@<public-ip>:/tmp/
ssh -i <your-key.pem> ubuntu@<public-ip> 'sudo install -o root -g root -m 0644 /tmp/my-photo.jpg /var/lib/cantaloupe/images/ && rm /tmp/my-photo.jpg'
Here the demo image is copied under two new names, one in a subdirectory, requested, and removed again:
sudo install -o root -g root -m 0644 /var/lib/cantaloupe/images/sample.jpg /var/lib/cantaloupe/images/my-photo.jpg
sudo install -d -o root -g root -m 0755 /var/lib/cantaloupe/images/collection-a
sudo install -o root -g root -m 0644 /var/lib/cantaloupe/images/sample.jpg /var/lib/cantaloupe/images/collection-a/page-001.jpg
cd /dev/shm/iiif-demo
curl -K iiif.cfg -o /dev/null -w 'my-photo.jpg: %{http_code}\n' "$(cat base)my-photo.jpg/info.json"
curl -K iiif.cfg -o /dev/null -w 'collection-a/page-001.jpg: %{http_code}\n' "$(cat base)collection-a%2Fpage-001.jpg/info.json"
sudo rm -r /var/lib/cantaloupe/images/my-photo.jpg /var/lib/cantaloupe/images/collection-a
ls /var/lib/cantaloupe/images
my-photo.jpg: 200
collection-a/page-001.jpg: 200
README.txt
sample.jp2
sample.jpg
New files are served at once; there is nothing to restart. For large images, convert them to tiled pyramidal TIFF or JPEG 2000 first (for example with vips on your own machine): Cantaloupe then reads only the tiles and resolution level it needs instead of decoding the whole image for every request. You can delete the demo images once your own are in place.
Step 7: Using it from viewers and applications
Anything that speaks the IIIF Image API can use the instance: IIIF Presentation manifests (point each canvas's image service at https://<host>/iiif/3/<file>), and viewers such as Mirador, Universal Viewer and OpenSeadragon.
Every request needs the Basic Auth password, so the client has to be able to send it:
- Server-side applications (a manifest generator, a thumbnail job, your own web application's back end) send it directly, as
curldoes above. This is the simplest and safest pattern: keep 443 open only to those servers. - Browser viewers on your own site cannot send a Basic Auth password with the cross-origin requests a viewer makes for
info.json, and you should not put the password in a public web page. Serve the IIIF requests through a back end of yours (a reverse proxy that adds theAuthorizationheader for your users), and keep this instance's 443 open only to that back end.
Cantaloupe adds Access-Control-Allow-Origin: * to its IIIF responses, as the IIIF specification recommends, so a viewer on another site works once it reaches the API through such a proxy.
Step 8: Limits and caching
| Limit | Value | Where |
|---|---|---|
Size of a max request |
10,000,000 pixels (a larger image is scaled down to fit) | max_pixels in /etc/cantaloupe/cantaloupe.properties |
| Upscaling | refused (403): an explicit size is at most the original size |
max_scale = 1.0 |
| Request rate per client address | 50 per second, bursts of 200 | limit_req in /etc/nginx/conf.d/cloudimg-cantaloupe.conf |
| Concurrent connections per client address | 32 | limit_conn |
| Methods | GET, HEAD |
the IIIF location in the nginx site |
A client over the rate limit gets 429 Too Many Requests. Here 400 full-size requests at once (64 in parallel) from one address:
cd /dev/shm/iiif-demo
P="$(cat base)sample.jpg/full/max/0/default.jpg"
for i in $(seq 1 400); do echo "url = \"$P\""; echo "output = /dev/null"; echo 'write-out = "%{http_code}\n"'; done > burst.cfg
curl -K iiif.cfg --parallel --parallel-max 64 -K burst.cfg | sort | uniq -c
37 200
363 429
The exact split depends on timing; an IIIF viewer requesting tiles as you pan and zoom stays well inside the limits.
Responses carry Cache-Control: max-age=2592000, private, so browsers and your own proxy may keep derivatives for 30 days. Cantaloupe keeps no server-side derivative cache on disk; each request is rendered from the source image. Important settings as shipped:
sudo grep -E '^(source\.static|source\.delegate|delegate_script\.enabled|FilesystemSource\.BasicLookupStrategy\.path_prefix|max_pixels|max_scale|endpoint\.(admin|api|health|iiif\.1)\.enabled|http\.host|http\.port|cache\.server\.derivative\.enabled|print_stack_trace_on_error_pages) ' /etc/cantaloupe/cantaloupe.properties
http.host = 127.0.0.1
http.port = 8182
max_pixels = 10000000
max_scale = 1.0
print_stack_trace_on_error_pages = false
delegate_script.enabled = false
endpoint.iiif.1.enabled = false
endpoint.admin.enabled = false
endpoint.api.enabled = false
endpoint.health.enabled = false
source.static = FilesystemSource
source.delegate = false
FilesystemSource.BasicLookupStrategy.path_prefix = /var/lib/cantaloupe/images/
cache.server.derivative.enabled = false
To change a Cantaloupe setting, edit the file (it is root:cantaloupe, mode 0640) and run sudo systemctl restart cantaloupe. Turning on a remote source (HttpSource, S3Source, AzureStorageSource), the delegate script or the admin endpoints undoes protections described in this guide; the systemd sandbox also blocks Cantaloupe's outbound network traffic, so a remote source would need that changed too.
When you are done with the examples, remove the work directory:
rm -rf /dev/shm/iiif-demo
Step 9: Your own host name and certificate
On every boot cantaloupe-set-host works out the name to serve: the name you set, else the instance's public DNS name from the instance metadata service (IMDSv2). Every IIIF id Cantaloupe writes is built from that configured name, never from the request:
systemctl is-enabled cantaloupe-set-host.service
sudo cantaloupe-set-hostname --boot
enabled
cantaloupe-set-hostname: unchanged (ec2-203-0-113-10.compute-1.amazonaws.com)
Use your own DNS name. Point a DNS record at the instance (an Elastic IP keeps the address fixed), then:
$ sudo cantaloupe-set-hostname images.example.com
cantaloupe-set-hostname: the IIIF service is https://images.example.com/iiif/3/ (certificate SHA-256 1D:81:28:66:9E:61:18:CC:D4:CD:CD:7E:3D:8F:97:FC:3F:C9:50:3C:A9:7C:77:2D:34:8F:A4:F1:C6:60:A3:D3)
The name is kept in /etc/cloudimg/cantaloupe-hostname for every later boot, the credentials file is updated, nginx serves that name and redirects the instance's other addresses to it, and every info.json id becomes https://images.example.com/iiif/.... Because the certificate in place was the one cloudimg generated, it was re-created for the new name.
Which certificates are replaced. Only a certificate cloudimg generated is ever re-created: its SHA-256 fingerprint is recorded, root owned, in /var/lib/cloudimg/cantaloupe-cert.sha256, and a certificate is treated as cloudimg's only while it matches that record:
sudo cat /var/lib/cloudimg/cantaloupe-cert.sha256
openssl x509 -in /etc/ssl/cantaloupe/cantaloupe.crt -noout -fingerprint -sha256
3F:76:70:FE:F2:5D:BA:37:A1:D4:BE:C7:50:FE:41:07:55:49:49:09:EE:B1:79:14:65:FB:82:3C:4A:E7:2C:E3
sha256 Fingerprint=3F:76:70:FE:F2:5D:BA:37:A1:D4:BE:C7:50:FE:41:07:55:49:49:09:EE:B1:79:14:65:FB:82:3C:4A:E7:2C:E3
Install your own certificate (from your CA, or Let's Encrypt with a DNS challenge) by putting the full chain and the key at the same paths. Anything there that does not match the record, including a symlink to a chain you manage elsewhere, is yours and is never replaced: not on boot, not when the name changes:
sudo install -m 0644 -o root -g root fullchain.pem /etc/ssl/cantaloupe/cantaloupe.crt
sudo install -m 0600 -o root -g root privkey.pem /etc/ssl/cantaloupe/cantaloupe.key
sudo nginx -t && sudo systemctl reload nginx
After that, a name change keeps your files:
$ sudo cantaloupe-set-hostname images.example.com
cantaloupe-set-hostname: the certificate in /etc/ssl/cantaloupe is not one this helper minted: it is kept unchanged
cantaloupe-set-hostname: the IIIF service is https://images.example.com/iiif/3/ (certificate SHA-256 <your certificate's fingerprint>)
Go back to a generated certificate by removing both files and running the helper with the name to serve:
$ sudo rm /etc/ssl/cantaloupe/cantaloupe.crt /etc/ssl/cantaloupe/cantaloupe.key
$ sudo cantaloupe-set-hostname images.example.com
To return to the instance's own public name on every boot, sudo rm /etc/cloudimg/cantaloupe-hostname && sudo cantaloupe-set-hostname --boot.
Step 10: Updates
Ubuntu packages, OpenJDK, OpenJPEG and nginx included, are updated by unattended security upgrades; to update everything now run sudo apt-get update && sudo apt-get upgrade, then sudo systemctl restart cantaloupe so Cantaloupe runs on the updated Java. Cantaloupe itself is not an apt package: it is the release jar at /opt/cantaloupe/cantaloupe-5.0.8.jar, and apt never changes it:
systemctl is-enabled unattended-upgrades apt-daily-upgrade.timer
dpkg -S /opt/cantaloupe/cantaloupe-5.0.8.jar 2>&1 || true
enabled
enabled
dpkg-query: no path found matching pattern /opt/cantaloupe/cantaloupe-5.0.8.jar
cloudimg publishes a new version of this listing when Cantaloupe has a new release or a security fix. To move, launch the new version and copy /var/lib/cantaloupe/images across (or attach a snapshot of the old root volume to the new instance and copy from it).
Security notes
- Local images only (no server-side request forgery).
source.static = FilesystemSourcewithsource.delegate = falseand the delegate script off: an identifier is only ever a file name below/var/lib/cantaloupe/images/. A URL, ans3://address or a../path given as the identifier is looked up as a file name and not found (Step 5). - Cantaloupe has no outbound network. Its systemd unit allows IP traffic to the loopback addresses only (
IPAddressAllow=localhost,IPAddressDeny=any), so even a changed configuration could not reach another host or the metadata service. - Instance metadata. Cantaloupe and nginx are denied the EC2 instance metadata service (169.254.169.254 and its IPv6 address) by systemd. Launch with IMDSv2 required (
HttpTokens=required), as in Step 1. - Authentication and methods. Every IIIF request and the landing page need the Basic Auth password; only
GETandHEADreach Cantaloupe. nginx sends Cantaloupe the configured host name and the real client address (X-Forwarded-Foris set from the connection, never passed through), drops client-supplied forwarding headers, and strips theAuthorizationandCookieheaders. - Nothing else exposed. The admin control panel, the REST API, the health endpoint and the IIIF 1 endpoint are off, and nginx returns
404for every path but/iiif/3/,/iiif/2/, the landing page and/healthz(for load balancer checks, answered by nginx without the password). Requests for host names the instance does not serve are dropped. - Sandboxed process. Cantaloupe runs as the unprivileged
cantaloupeaccount with a read-only view of the system (it can write only its temporary directory), a private/tmp, no capabilities and a system call filter. It can read its configuration; nginx's account cannot. - Exact listeners. Only 22, 80 and 443 listen on the network (Step 3). Open them in the security group only to the networks that need them, and use the password over HTTPS only.
The metadata block and the network sandbox, as systemd applies them:
systemctl show cantaloupe nginx -p Id -p IPAddressDeny -p IPAddressAllow
IPAddressAllow=127.0.0.0/8 ::1/128
IPAddressDeny=::/0 0.0.0.0/0
Id=cantaloupe.service
IPAddressAllow=
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=nginx.service
Build provenance and licence
Cantaloupe 5.0.8 is the upstream release (GitHub release v5.0.8, 28 October 2026). The image build downloads the release archive, checks its SHA-512, and extracts only the application jar (checked by SHA-256), the licence, the third-party notices and the sample configuration. The archive's deps/ directory, which holds Kakadu JPEG 2000 libraries under a proprietary licence, is never extracted; JPEG 2000 is decoded with OpenJPEG from Ubuntu instead.
cat /usr/share/doc/cloudimg/cantaloupe-BUILD-INFO.txt
sha256sum /opt/cantaloupe/cantaloupe-5.0.8.jar
ls /usr/share/doc/cantaloupe/
Cantaloupe 5.0.8 on Ubuntu 24.04 (cloudimg AWS bundle member media)
release archive https://github.com/cantaloupe-project/cantaloupe/releases/download/v5.0.8/cantaloupe-5.0.8.zip sha512 30cbbc6b6b8e54b6c1d60e66af05e68c304fa75431da51be773d591309e319b2c24f8f61556ba1beb972b0fadc6ed9ca580d7dae7c4f917c4eb83d3e1e87cad4; NCSA licence, LICENSE.txt sha256 51c929fcfed0ed03bd2f55287dec6b2b933469c6875624d319a6ea8f665dc0fc -> /usr/share/doc/cantaloupe/
jar /opt/cantaloupe/cantaloupe-5.0.8.jar sha256 63e2984d6af504b2bb133af5a8df3115614130a914caf145ba1a2c31e3a1c912; third-party notices /usr/share/doc/cantaloupe/LICENSE-3RD-PARTY.txt sha256 ffa5e5fba76ce2e3cfb0b7c529d89c63e10df1d242d30f60d66d8448214e78de
not extracted from the archive: deps/ (Kakadu native libraries, proprietary); JPEG 2000 via OpenJpegProcessor + opj_decompress
java 21.0.12.1+1-1~24.04.4 (openjdk-21-jre-headless, Ubuntu main)
opj_decompress 2.5.0-2ubuntu0.5 (libopenjp2-tools, Ubuntu universe; source openjpeg2 in main)
nginx 1.24.0-2ubuntu7.19 (Ubuntu main)
built 2026-11-07T09:43:18Z
63e2984d6af504b2bb133af5a8df3115614130a914caf145ba1a2c31e3a1c912 /opt/cantaloupe/cantaloupe-5.0.8.jar
LICENSE-3RD-PARTY.txt
LICENSE.txt
cantaloupe.properties.sample
/usr/share/doc/cantaloupe/LICENSE.txt: Cantaloupe's University of Illinois/NCSA Open Source License./usr/share/doc/cantaloupe/LICENSE-3RD-PARTY.txt: the notices of the libraries bundled in the jar./usr/share/doc/cantaloupe/cantaloupe.properties.sample: upstream's full configuration reference, with every key documented.
Libraries in the jar. A vulnerability scan of the jar lists advisories in bundled libraries that are not reachable in this configuration: Netty and the cloud storage clients (used only by the S3 and Azure sources and caches, which are off and could not connect out anyway), Jackson's JSON parser (no client-supplied JSON is parsed: the API, the delegate and the derivative cache are off), Guava (no Java deserialization of client data) and build-time libraries. cloudimg publishes a new version when Cantaloupe ships updated libraries.
Cantaloupe's source: github.com/cantaloupe-project/cantaloupe (tag v5.0.8). Cantaloupe is an open source project of the University of Illinois at Urbana-Champaign Library; IIIF is a trademark of the IIIF Consortium. cloudimg is not affiliated with or endorsed by either.
Troubleshooting
401 Unauthorized. The password is wrong or missing; read it again from/root/cantaloupe-credentials.txt. Aftercantaloupe-set-passwordthe old one is refused at once.404 Not Foundfor an image. The file is not in/var/lib/cantaloupe/images, the name differs in case, a subdirectory slash is not encoded as%2F, or thecantaloupeaccount cannot read the file (sudo -u cantaloupe head -c1 /var/lib/cantaloupe/images/<file> >/dev/null).403 Forbiddenfor a size. The request asks for more than the original size (^upscaling), which is refused (Step 8).429 Too Many Requests. A client is over the rate limits (Step 8).idsininfo.jsonname the wrong host. Set the name clients use withcantaloupe-set-hostname(Step 9).- Certificate errors. The client does not trust this instance's certificate, or connects with a name the certificate does not carry. Trust
cantaloupe.crtor install your own (Step 9). - The connection is dropped with no answer. The request named a host the instance does not serve. Use the name in
CANTALOUPE_URL, or set yours withcantaloupe-set-hostname. - Logs:
journalctl -u cantaloupe,/var/log/nginx/access.logand/var/log/nginx/error.log, and/var/log/cloudimg/cantaloupe-firstboot.log.
Support
cloudimg images come with 24/7 support. Email support@cloudimg.co.uk with your AWS account ID, the instance ID and the output of sudo /usr/local/sbin/cantaloupe-verify.sh. For Cantaloupe itself, see the Cantaloupe user manual and the IIIF Image API.