Deployment guide

Cantaloupe IIIF Image Server 5.0.8

Overview

This guide covers launching and using the Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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. A deep-zoom viewer therefore downloads only 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 is at https://<host>/iiif/2/. Both are served over HTTPS only. Port 80 only redirects.

What you get:

  • 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/images and 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:8182 behind nginx. It runs as the unprivileged cantaloupe account 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 max size request is capped at 10 megapixels. nginx rate-limits each client and accepts only GET and HEAD. 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 404 for 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. OpenJPEG from Ubuntu decodes JPEG 2000 images. 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.
  • Cantaloupe 5.0.8 (University of Illinois/NCSA Open Source License), OpenJDK 21, OpenJPEG and nginx from Ubuntu 24.04, 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.
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, choose your Region, then choose Launch through EC2.

Choose m5.large, your key pair, a security group as described above, and a subnet. Your images live on the root volume. If your collection is large, raise its size under Configure storage.

Alternatively, launch with the AWS CLI. Replace the values in angle brackets. This command 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}]'
2

Connecting to your instance

Connect to Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 over SSH as the ubuntu user:

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot works out the instance's name, generates the certificate and the password, and starts Cantaloupe. It proves a IIIF round trip over HTTPS and only then opens the web server. This 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, either first boot has not finished or it failed. In both cases the IIIF API is never opened. Check systemctl status cantaloupe-firstboot and sudo tail -40 /var/log/sac/cantaloupe-firstboot.log.

4

Get your IIIF address and password

sudo cat /root/cantaloupe-credentials.txt

Only root can read the file. It 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 except 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. It also checks that all of these requests are refused: requests without the password, requests with a wrong password, writes, upscaling, paths outside the images directory, and remote URLs.

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 the password 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 password:

$ read -rs NEWPW && printf '%s\n' "$NEWPW" | sudo cantaloupe-set-password - ; unset NEWPW cantaloupe-set-password: password replaced (sudo cat /root/cantaloupe-credentials.txt)
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. They are 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, create a private work directory in memory (/dev/shm), a copy of the instance's certificate, and a curl configuration file that holds the password. This keeps the password off the command line and out of 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 requests 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 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. The server refuses a request with 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 except 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
6

Add your own images

Copy your images into /var/lib/cantaloupe/images. The cantaloupe account must be able to read them. Root-owned files with mode 0644 (directories 0755) are the simplest option.

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. Address a file in a subdirectory with the slash encoded as %2F.

For example, copy files from your own machine:

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'

This example copies the demo image under two new names, one in a subdirectory. It requests both and removes them 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, and 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.

7

Using it from viewers and applications

Anything that speaks the IIIF Image API can use the instance. This includes IIIF Presentation manifests, where you 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 the password directly, as curl does 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, such as a reverse proxy that adds the Authorization header for your users. 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. A viewer on another site therefore works once it reaches the API through such a proxy.

8

Limits and caching

These limits apply:

  • Size of a max request: 10,000,000 pixels, and a larger image is scaled down to fit (max_pixels in /etc/cantaloupe/cantaloupe.properties)
  • Upscaling: refused with 403, so an explicit size is at most the original size (max_scale = 1.0)
  • Request rate per client address: 50 per second, with bursts of 200 (limit_req in /etc/nginx/conf.d/sac-cantaloupe.conf)
  • Concurrent connections per client address: 32 (limit_conn)
  • Methods: GET and HEAD (the IIIF location in the nginx site)

A client over the rate limit gets 429 Too Many Requests. This example sends 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 that requests 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, and renders each request from the source image.

These are the 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
9

Your own host name and certificate

On every boot, cantaloupe-set-host works out the name to serve. It uses the name you set, or else the instance's public DNS name from the instance metadata service (IMDSv2). Every IIIF id that 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 run:

$
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/sac/cantaloupe-hostname for every later boot, and the credentials file is updated. nginx serves that name and redirects the instance's other addresses to it. Every info.json id becomes https://images.example.com/iiif/.... The certificate in place was the one SecureAnyCloud generated, so it was re-created for the new name.

Which certificates are replaced. Only a certificate SecureAnyCloud generated is ever re-created. Its SHA-256 fingerprint is recorded, root-owned, in /var/lib/sac/data/cantaloupe-cert.sha256. A certificate counts as SecureAnyCloud's only while it matches that record:

sudo cat /var/lib/sac/data/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 from 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, either on boot or 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, run sudo rm /etc/sac/cantaloupe-hostname && sudo cantaloupe-set-hostname --boot.

10

Updates

Unattended security upgrades update Ubuntu packages, including OpenJDK, OpenJPEG and nginx. To update everything now, run sudo apt-get update && sudo apt-get upgrade, then sudo systemctl restart cantaloupe so that 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

SecureAnyCloud 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. Alternatively, 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 = FilesystemSource with source.delegate = false and the delegate script off. An identifier is only ever a file name below /var/lib/cantaloupe/images/. A URL, an s3:// 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. systemd denies Cantaloupe and nginx access to the EC2 instance metadata service (169.254.169.254 and its IPv6 address). 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, and only GET and HEAD reach Cantaloupe. nginx sends Cantaloupe the configured host name and the real client address. It sets X-Forwarded-For from the connection and never passes it through. It also drops client-supplied forwarding headers and strips the Authorization and Cookie headers.
  • Nothing else exposed. The admin control panel, the REST API, the health endpoint and the IIIF 1 endpoint are off. nginx returns 404 for every path except /iiif/3/, /iiif/2/, the landing page and /healthz. nginx answers /healthz, for load balancer checks, without the password. Requests for host names the instance does not serve are dropped.
  • Sandboxed process. Cantaloupe runs as the unprivileged cantaloupe account 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, and 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/sac/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 (sac 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. SecureAnyCloud 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. SecureAnyCloud 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. After cantaloupe-set-password the old one is refused at once.
  • 404 Not Found for 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 the cantaloupe account cannot read the file (sudo -u cantaloupe head -c1 /var/lib/cantaloupe/images/<file> >/dev/null).
  • 403 Forbidden for 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).
  • ids in info.json name the wrong host. Set the name clients use with cantaloupe-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.crt or 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 with cantaloupe-set-hostname.
  • Logs: journalctl -u cantaloupe, /var/log/nginx/access.log and /var/log/nginx/error.log, and /var/log/sac/cantaloupe-firstboot.log.

Support

SecureAnyCloud images come with 24/7 support. Email sales@secureanycloud.com 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.

Deploy on AWS

Launch Cantaloupe IIIF Image Server 5.0.8 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS