Applications AWS

Apache Tika Server 3.3.3 on AWS User Guide

| Product: Apache Tika Server 3.3.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Apache Tika Server 3.3.3 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Documents, Forms and Scheduling listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Apache Tika is the Apache Software Foundation's content analysis toolkit. Tika Server wraps it in a REST API: you upload a document and get back its plain text, its metadata as JSON, or both, for PDF, Microsoft Office and OpenDocument files, HTML, e-mail, archives, images and more than a thousand other formats. Search engines, document management systems, e-discovery and RAG pipelines use it as their text extraction service.

Upstream Tika Server has no authentication, listens in plain HTTP, and can fetch remote resources when its "unsecure features" are switched on, so it must never be exposed as it ships. The cloudimg image makes it safe to run on a public address:

  • HTTPS from the first boot. nginx serves the API on port 443 with a certificate generated on your instance's first boot, naming the instance's DNS name and IP addresses. There is no port 80. Requests for any other host name are dropped.
  • A password on every endpoint. Every Tika endpoint needs HTTP Basic Auth with the user admin and a password generated for your instance at first boot (root only, in /root/tika-server-credentials.txt). Only /healthz is open, for load balancer checks. No password ships in the image, and nginx refuses to start until first boot has created one.
  • Tika on localhost only. Tika listens on 127.0.0.1:9998; nginx is the only way in from the network, and it never passes your password on to Tika.
  • No fetching. Tika's unsecure features stay off (enableUnsecureFeatures=false): no fetchers, emitters, /pipes or /async, no fileUrl. Only the extraction endpoints are loaded. XML external entities in uploaded documents are not resolved.
  • No network egress for the parser. Tika runs as its own unprivileged user in a systemd sandbox that may talk to localhost only, so a malicious document cannot make it reach the internet, your VPC or the instance metadata. nginx and the first boot service are blocked from the instance metadata service too, and the AMI requires IMDSv2.
  • Request limits. Uploads are limited to 100 MiB and streamed to Tika (nginx does not spool them to disk), each client address may hold 16 connections, and Tika's parsing process is restarted if a document makes it run out of memory or parse for longer than 5 minutes.
  • The real client address. nginx overwrites X-Forwarded-For and X-Real-IP with the address of the connection.
  • The official release, verified against its published SHA-512 checksum, on a fully patched Ubuntu 24.04 LTS, with unattended security upgrades switched on.

What is on the image: Apache Tika Server 3.3.3 (the official tika-server-standard JAR in /opt/tika, licence and notice in /usr/share/doc/tika), OpenJDK 17 (headless runtime), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large (Tika sizes its parsing heap at half the instance memory: 4 GiB on m5.large; choose more memory for very large documents)

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
  • A security group that allows TCP 22 (SSH) from your administrators and TCP 443 (the API) from the hosts that call Tika only. Tika has no reason to be open to the whole internet.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Apache Tika Server 3.3.3 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose your instance type, key pair, subnet and a security group as above.

With the AWS CLI (replace the values in angle brackets), create the security group and launch:

aws ec2 create-security-group --region <region> --vpc-id <your-vpc> \
  --group-name tika --description "Apache Tika: SSH from admins, HTTPS from API clients"
aws ec2 authorize-security-group-ingress --region <region> --group-id <sg-id> --ip-permissions \
  'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=<your-admin-cidr>}]' \
  'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=<your-client-cidr>}]'
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 <sg-id> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=apache-tika}]'

To serve your own DNS name from the first boot, add user data that writes it before first boot runs, for example #!/bin/sh followed by mkdir -p /etc/cloudimg && echo tika.example.com > /etc/cloudimg/tika-hostname. Without it the instance serves its EC2 public DNS name (or, without one, its IP address); you can change it later (Step 10).

Step 2: Connect to your instance

Version SSH user
Apache Tika Server 3.3.3 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and the API password, then Tika and nginx start:

systemctl is-active tika nginx
sudo test -f /var/lib/cloudimg/tika-firstboot.done && echo FIRSTBOOT_DONE
active
active
FIRSTBOOT_DONE

nginx is the only service on the network (port 443, plus SSH). Tika's own port 9998 is bound to 127.0.0.1; the other local listeners are the operating system's DNS stub, time sync and DHCP client:

sudo ss -lntup | awk 'NR>1 {print $1, $5, $7}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort
tcp 0.0.0.0:22 users:(("sshd",...),("systemd",...))
tcp 0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...))
tcp 127.0.0.53%lo:53 users:(("systemd-resolve",...))
tcp 127.0.0.54:53 users:(("systemd-resolve",...))
tcp [::]:22 users:(("sshd",...),("systemd",...))
tcp [::]:443 users:(("nginx",...),("nginx",...),("nginx",...))
tcp [::ffff:127.0.0.1]:9998 users:(("java",...))
udp 127.0.0.1:323 users:(("chronyd",...))
udp 127.0.0.53%lo:53 users:(("systemd-resolve",...))
udp 127.0.0.54:53 users:(("systemd-resolve",...))
udp 172.31.88.254%ens5:68 users:(("systemd-network",...))
udp [::1]:323 users:(("chronyd",...))

Step 4: Read your URL and password

The credentials file is readable by root only. This prints it with the password hidden; sudo cat /root/tika-server-credentials.txt shows it:

sudo sed -E 's/^(TIKA_PASSWORD=).*/\1<hidden>/' /root/tika-server-credentials.txt
# Apache Tika Server 3.3.3 - generated on first boot by tika-firstboot.service
# This password is unique to this instance; none was baked into the image. Keep this file secret.

TIKA_URL=https://ec2-100-55-11-151.compute-1.amazonaws.com/
TIKA_USERNAME=admin
TIKA_PASSWORD=<hidden>
#
# Every Tika endpoint needs HTTP Basic Auth with TIKA_USERNAME and TIKA_PASSWORD; https://ec2-100-55-11-151.compute-1.amazonaws.com/healthz is the
# only open path. The certificate is self signed (minted on this instance's first boot): pass curl --cacert with
# /etc/ssl/tika/tika.crt, or install your own certificate.
#   Extract text:      curl -u admin -T report.pdf  -H 'Accept: text/plain'       https://ec2-100-55-11-151.compute-1.amazonaws.com/tika
#   Extract metadata:  curl -u admin -T report.docx -H 'Accept: application/json' https://ec2-100-55-11-151.compute-1.amazonaws.com/meta
# New password:                  sudo tika-set-password
# Change the name it serves as:  sudo tika-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/apache-tika-server-3-3-3-on-ubuntu-24-04-aws/

The values above come from one test instance; yours are different.

Step 5: Trust the instance's certificate

Clients verify the instance's own certificate, /etc/ssl/tika/tika.crt (the certificate is public; its key is readable by root only). Copy it to every host that calls Tika, as here into your home directory, and check that it names this instance. Inside the VPC the public DNS name resolves to the private address, so the commands below also work on the instance itself:

sudo install -m 0644 /etc/ssl/tika/tika.crt ~/tika-ca.crt
openssl x509 -in ~/tika-ca.crt -noout -subject -enddate -ext subjectAltName
H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
curl -s --cacert ~/tika-ca.crt "https://$H/healthz"
subject=CN = ec2-100-55-11-151.compute-1.amazonaws.com, O = cloudimg
notAfter=Jan 22 04:28:04 2029 GMT
X509v3 Subject Alternative Name: 
    DNS:ec2-100-55-11-151.compute-1.amazonaws.com, IP Address:100.55.11.151, IP Address:172.31.88.254, IP Address:127.0.0.1, DNS:localhost
ok

Without the password, every Tika endpoint is refused:

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
curl -s --cacert ~/tika-ca.crt -o /dev/null -w 'no password: HTTP %{http_code}\n' "https://$H/version"
curl -s --cacert ~/tika-ca.crt -o /dev/null -w 'no password, upload: HTTP %{http_code}\n' -T /opt/tika/samples/cloudimg-sample.pdf "https://$H/tika"
no password: HTTP 401
no password, upload: HTTP 401

Step 6: Store the password for curl

Keep the password out of your shell history and process list: put it in a netrc file that only you can read, and give curl --netrc-file. (Interactively, curl -u admin also works: curl then asks for the password.)

C=/root/tika-server-credentials.txt
H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' $C); P=$(sudo sed -n 's/^TIKA_PASSWORD=//p' $C)
( umask 077; printf 'machine %s login admin password %s\n' "$H" "$P" > ~/.tika-netrc )
ls -l ~/.tika-netrc | awk '{print $1, $NF}'
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt "https://$H/version"; echo
-rw------- /root/.tika-netrc
Apache Tika 3.3.3

Step 7: Extract text from a document

Upload a document with PUT (curl -T) to /tika and ask for text/plain. The image includes two small test documents in /opt/tika/samples, a PDF and a Word (DOCX) file; use your own files the same way:

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T /opt/tika/samples/cloudimg-sample.pdf -H 'Accept: text/plain' "https://$H/tika" | sed '/^[[:space:]]*$/d'
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T /opt/tika/samples/cloudimg-sample.docx -H 'Accept: text/plain' "https://$H/tika" | sed '/^[[:space:]]*$/d'
CloudimgTikaProbe Apache Tika on cloudimg extracts text and metadata from this document.
CloudimgTikaProbe Apache Tika on cloudimg extracts text and metadata from this document.

Ask /tika for text/html instead to get the document as XHTML with its structure (headings, paragraphs, tables).

Step 8: Metadata, both at once, and detection

/meta returns the document's metadata as JSON: its media type, title, author, page count and so on, and which parser read it:

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T /opt/tika/samples/cloudimg-sample.docx -H 'Accept: application/json' "https://$H/meta" | jq '{"Content-Type", "dc:title", "dc:creator", "X-TIKA:Parsed-By"}'
{
  "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "dc:title": "cloudimg Tika sample",
  "dc:creator": "cloudimg",
  "X-TIKA:Parsed-By": [
    "org.apache.tika.parser.DefaultParser",
    "org.apache.tika.parser.microsoft.ooxml.OOXMLParser",
    "org.apache.tika.parser.microsoft.ooxml.xwpf.XWPFEventBasedWordExtractor"
  ]
}

/rmeta/text returns metadata and text together, one JSON object per document, including every document embedded in it (attachments of an e-mail, files in a ZIP, images in a Word file). /detect/stream names the media type and /language/string guesses the language of a text:

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T /opt/tika/samples/cloudimg-sample.pdf "https://$H/rmeta/text" | jq '.[0] | {"Content-Type", "xmpTPg:NPages", "pdf:PDFVersion", content: (."X-TIKA:content" | gsub("\\s+"; " ") | ltrimstr(" "))}'
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T /opt/tika/samples/cloudimg-sample.docx "https://$H/detect/stream"; echo
printf 'Apache Tika detects the language of this sentence.' | curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T - "https://$H/language/string"; echo
{
  "Content-Type": "application/pdf",
  "xmpTPg:NPages": "1",
  "pdf:PDFVersion": "1.4",
  "content": "CloudimgTikaProbe Apache Tika on cloudimg extracts text and metadata from this document. "
}
application/vnd.openxmlformats-officedocument.wordprocessingml.document
en

The other endpoints are /unpack (the embedded files of a document, as a ZIP), /parsers, /detectors and /mime-types (what this Tika supports) and /version. The Tika Server documentation describes each one and the request headers that tune parsing.

Step 9: Check the security posture

A wrong password is refused like a missing one, a request for any host name other than the instance's own is dropped, and nothing listens on port 80:

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
printf 'machine %s login admin password %s\n' "$H" "wrong-$(openssl rand -hex 8)" | \
  curl -s --netrc-file /dev/stdin --cacert ~/tika-ca.crt -o /dev/null -w 'wrong password: HTTP %{http_code}\n' "https://$H/version"
curl -sk -o /dev/null -w 'unknown host name: HTTP %{http_code} (000 = connection closed)\n' --resolve evil.example:443:127.0.0.1 https://evil.example/version || true
curl -s -o /dev/null -w 'port 80: HTTP %{http_code} (000 = nothing listening)\n' http://127.0.0.1/ || true
wrong password: HTTP 401
unknown host name: HTTP 000 (000 = connection closed)
port 80: HTTP 000 (000 = nothing listening)

Tika's configuration is /etc/tika/tika-config.xml. It binds Tika to localhost, keeps the unsecure features off and loads only the extraction endpoints. Asked directly on its local port, Tika refuses a fetch request for a local file (422, nothing fetched) and has no /pipes or /async endpoint:

grep -oE '<(host|port|enableUnsecureFeatures|endpoint)>[^<]*' /etc/tika/tika-config.xml | sed 's/^<//; s/>/=/' | paste -sd' ' -
curl -s -o /dev/null -w 'fetcher request for /etc/passwd: HTTP %{http_code}\n' -X PUT -H 'fetcherName: fsf' -H 'fetchKey: /etc/passwd' http://127.0.0.1:9998/tika
for p in pipes async; do curl -s -o /dev/null -w "POST /$p: HTTP %{http_code}\n" -X POST -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:9998/$p; done
host=127.0.0.1 port=9998 enableUnsecureFeatures=false endpoint=tika endpoint=rmeta endpoint=meta endpoint=detect endpoint=language endpoint=unpack endpoint=mime endpoint=detectors endpoint=parsers endpoint=version
fetcher request for /etc/passwd: HTTP 422
POST /pipes: HTTP 404
POST /async: HTTP 404

Port 9998 answers without a password, but only on the instance itself: anyone who can run commands there is already an administrator. Never open 9998 in a security group or bind Tika to another address; go through nginx on 443.

An uploaded XML document that tries to include a local file through an external entity gets nothing back:

X=$(mktemp)
printf '<?xml version="1.0"?>\n<!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]>\n<r>external entity test: &x;</r>\n' > "$X"
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -T "$X" -H 'Content-Type: application/xml' -H 'Accept: text/plain' \
  "https://$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)/tika" | sed '/^[[:space:]]*$/d'
rm -f "$X"
 external entity test: 

Tika may talk to localhost only (every other address is denied), and nginx and the first boot service are denied the instance metadata addresses:

systemctl show -p IPAddressAllow -p IPAddressDeny tika.service nginx.service tika-firstboot.service | sed '/^$/d'
IPAddressAllow=::1/128 127.0.0.0/8
IPAddressDeny=::/0 0.0.0.0/0
IPAddressAllow=
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressAllow=
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

Tika runs as the unprivileged tika user. Its watchdog process starts the parsing process with a heap of half the instance memory and restarts it if a document exhausts that memory:

for p in $(pgrep -u tika java); do printf '%s pid %s: %s\n' "$(ps -o user= -p "$p")" "$p" "$(tr '\0' ' ' < /proc/$p/cmdline | grep -oE '(-Xmx[^ ]+|-XX:MaxRAMPercentage=[^ ]+|-XX:\+ExitOnOutOfMemoryError|TikaServerProcess -h [^ ]+ -p [^ ]+|-jar [^ ]+ -c [^ ]+)' | paste -sd' ' -)"; done
tika pid 1035: -Xmx128m -jar /opt/tika/tika-server.jar -c /etc/tika/tika-config.xml
tika pid 1353: -XX:MaxRAMPercentage=50 -XX:+ExitOnOutOfMemoryError TikaServerProcess -h 127.0.0.1 -p 9998

Uploads larger than 100 MiB are refused before they reach Tika, and every response carries security headers (Tika's HTML output repeats document content, so browsers are told never to run anything in it):

H=$(sudo sed -n 's#^TIKA_URL=https://\(.*\)/$#\1#p' /root/tika-server-credentials.txt)
head -c 110000000 /dev/zero | curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -o /dev/null \
  -w 'a 110 MB upload: HTTP %{http_code}\n' -X PUT -H 'Content-Type: application/octet-stream' --data-binary @- "https://$H/tika"
curl -s --netrc-file ~/.tika-netrc --cacert ~/tika-ca.crt -D - -o /dev/null "https://$H/version" | grep -iE '^(strict|x-content|x-frame|content-security|cache-control|server)' | tr -d '\r'
a 110 MB upload: HTTP 413
server: nginx
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-frame-options: DENY
content-security-policy: default-src 'none'; frame-ancestors 'none'; sandbox
cache-control: no-store

Step 10: Use the API from your own hosts

Copy ~/tika-ca.crt to the host that calls Tika and use the URL from the credentials file, or the instance's public IP address (both are in the certificate). From your workstation, with the security group allowing your address on 443:

curl --cacert tika-ca.crt -u admin -T report.pdf -H 'Accept: text/plain' https://<public-ip>/tika

Client libraries that call a Tika Server, for example the Python tika package (set TIKA_SERVER_ENDPOINT to the HTTPS URL), are configured the same way: the URL, the user admin, the password and the CA certificate.

Your own DNS name and certificate. Point a DNS record at the instance (an Elastic IP is recommended), then make nginx serve that name with a new certificate:

sudo tika-set-hostname tika.example.com

The name is kept in /etc/cloudimg/tika-hostname. To use a certificate from a public or private certificate authority instead, replace /etc/ssl/tika/tika.crt and /etc/ssl/tika/tika.key (key mode 0600, owner root) and run sudo systemctl reload nginx, or put an Application Load Balancer with an AWS Certificate Manager certificate in front of port 443.

Step 11: Change the password

Generate a new random password (written to /root/tika-server-credentials.txt), or set your own from standard input so it never appears on a command line. nginx reads the password hash on every request, so no restart is needed:

sudo tika-set-password
read -rs NEWPW && printf '%s\n' "$NEWPW" | sudo tika-set-password --stdin; unset NEWPW

Step 12: Tuning

  • Larger uploads. Raise client_max_body_size 100m; in /etc/nginx/sites-available/cloudimg-tika, then sudo nginx -t && sudo systemctl reload nginx. Make sure the instance has the memory and disk for the documents you allow.
  • Memory. The parsing process uses half the instance memory (-XX:MaxRAMPercentage=50 under forkedJvmArgs in /etc/tika/tika-config.xml). The simplest way to give Tika more is a larger instance type; to change the share, edit the value and run sudo systemctl restart tika.
  • Time limit. A single parse may take up to 5 minutes (taskTimeoutMillis in the same file) before Tika restarts its parsing process; nginx waits slightly longer.

Keep enableUnsecureFeatures set to false. Turning it on lets anyone who knows the password make Tika read files and fetch URLs through fetchers; if you need Tika's pipes for batch work, run them on a private instance with no internet access.

Step 13: Security recommendations and updates

  • Allow TCP 443 only from the hosts that call Tika, and TCP 22 only from your administrators.
  • Keep the password in a secret store (for example AWS Secrets Manager) and rotate it with tika-set-password.
  • Unattended security upgrades stay on for Ubuntu, OpenJDK and nginx. Tika is the official JAR in /opt/tika. Watch the Apache Tika news page for new releases and security announcements; to upgrade, download the new tika-server-standard JAR from Maven Central, check its checksum, point the /opt/tika/tika-server.jar link at it and run sudo systemctl restart tika.

Support and licensing

Apache Tika is developed by the Apache Software Foundation and distributed under the Apache License 2.0 (/usr/share/doc/tika/LICENSE and NOTICE). The image ships the unmodified official JAR, verified against its published checksum; the README also records the versions of the libraries bundled in it that carry security fixes:

head -6 /usr/share/doc/tika/README.cloudimg.txt
Apache Tika Server 3.3.3 (https://tika.apache.org/), Apache License 2.0 (LICENSE, NOTICE in this directory).
This image runs the unmodified official tika-server-standard JAR:
  https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.3.3/tika-server-standard-3.3.3.jar
  sha512 5242b1c9d7aa455bdb0fe101304b2c4a274882406f4d054af39cc91eef678aecb7bf31127d8cc692cd5f47a9f602f4840139f9e8e44a708e312b2ba59d1ebe89
  installed as /opt/tika/tika-server-standard-3.3.3.jar (symlink /opt/tika/tika-server.jar)
  bundled (advisory-fixed): jackson-core 2.22.3 jackson-databind 2.22.3 jsoup 1.23.2 log4j-api 2.26.1

cloudimg provides the packaging, the first boot automation, the HTTPS front end with per instance authentication and the hardening, with 24/7 support by email and live chat at support@cloudimg.co.uk.

All product names are trademarks of their respective owners. Apache, Apache Tika and Tika are trademarks of the Apache Software Foundation. cloudimg is not affiliated with or endorsed by the Apache Software Foundation.