TLS/SSL Configuration
Tika Server supports TLS for encrypted communication, in two flavours:
-
1-way TLS — the server presents its certificate; clients verify the server.
-
2-way TLS (mTLS) — both sides present certificates and authenticate each other.
Should you configure TLS here at all?
For most deployments, put Tika Server behind a reverse proxy (nginx, Apache httpd, Traefik) that terminates TLS, and run Tika itself on plain HTTP on localhost or a private network. That simplifies certificate management and gives you rate limiting and authentication for free. Tika Server has no built-in token support, so JWT / API-key / OAuth2 authentication has to come from a proxy or API gateway anyway.
Configure TLS directly on Tika Server (the rest of this page) when you cannot use a proxy, need client-certificate authentication, or prefer a single-process deployment. If you only need to restrict access and not encrypt it, network controls — binding to localhost, firewall rules, a private subnet — are simpler than either.
Quick Start
TLS settings live in tlsConfig inside the server section of tika-config.json. Pass the
file with -c/--config.
1-Way TLS
Server authenticates to clients; clients present no certificate.
{
"server": {
"host": "localhost",
"port": 9998,
"tlsConfig": {
"keyStoreType": "PKCS12",
"keyStoreFile": "/path/to/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
"clientAuthenticationRequired": false
}
}
}
2-Way TLS (mTLS)
Add a truststore holding the client certificates you trust, or the CA certificate that issues them, and require client authentication. A certificate issued by a CA is also checked for revocation; see Client certificate revocation.
{
"server": {
"host": "localhost",
"port": 9998,
"tlsConfig": {
"keyStoreType": "PKCS12",
"keyStoreFile": "/path/to/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
"trustStoreType": "PKCS12",
"trustStoreFile": "/path/to/server-truststore.p12",
"trustStorePassword": "${env:TRUSTSTORE_PASSWORD}",
"clientAuthenticationRequired": true
}
}
}
Configuration Reference
A tlsConfig block turns TLS on: there is no separate switch to forget, and the server never
falls back to plain HTTP. To serve plain HTTP, leave the block out. All properties below go in
server.tlsConfig.
Up to 4.1 the block also carried "active": true. It is deprecated in 4.2.0: true is
ignored with a warning, false is refused (remove the block instead), and the key is rejected
from 4.3.0.
|
| Property | Type | Default | Description |
|---|---|---|---|
|
string |
null |
Keystore format: |
|
string |
null |
Path to the server’s keystore, holding its private key and certificate. |
|
string |
null |
Password for the keystore; |
|
string |
null |
Truststore format: |
|
string |
null |
Path to the truststore holding trusted client certificates, or the CAs that issue them (2-way TLS).
Only used with |
|
string |
null |
Password for the truststore. |
|
boolean |
|
Require a client certificate trusted by the server. |
|
boolean |
|
Reject a CA-issued client certificate unless its revocation status can be confirmed (CRL or OCSP). See Client certificate revocation. |
|
integer |
|
Days before certificate expiration to log a warning. |
keyStoreType, keyStoreFile and keyStorePassword are all required.
Truststore properties are all-or-nothing: set all three or none.
A keystore or truststore that cannot be opened stops startup: the server never falls back to plain HTTP.
Client certificate revocation
With clientAuthenticationRequired, a client certificate issued by a CA in the truststore is
also checked for revocation, using the sources named in the certificate itself: its OCSP
responder (Authority Information Access) first, then its CRL distribution point if OCSP gives no
answer. No JVM flags are needed. The check fails closed: the client is rejected when the
certificate names no revocation source, when no source can be reached, or when one lists the
certificate as revoked. A certificate placed directly in the truststore is trusted as is; it is
not checked.
The check runs during the TLS handshake, so the server must be able to reach the OCSP responder or
CRL distribution point. One that does not answer holds that client’s handshake until the JDK
gives up: 15 seconds each to connect and to read, for OCSP and then again for the CRL. To shorten
that, set -Dcom.sun.security.ocsp.timeout, -Dcom.sun.security.ocsp.readtimeout,
-Dcom.sun.security.crl.timeout and -Dcom.sun.security.crl.readtimeout (in seconds). Only
certificates issued by a CA in the truststore reach this check: a certificate the server cannot
verify is rejected without any fetch.
If your CA publishes no CRL or OCSP responder, or the server cannot reach it, turn the check off. This is often the case for CAs that issue short-lived, automatically rotated certificates, such as cert-manager or SPIFFE/SPIRE, which rely on the short lifetime instead of revocation: with the check on, every client is rejected. With the check off, revoking a client means removing its certificate (or its CA) from the truststore, or letting a short-lived certificate expire:
"checkRevocation": false
The client sees only certificate_unknown; the server logs the reason at INFO (logger
org.apache.tika.server.core.ClientCertificateTrustManager), for example
client certificate rejected: CN=client-1: Certificate has been revoked.
| Through 4.1 the server checked revocation without enabling CRL fetching, so it rejected every CA-issued client certificate. Only certificates placed directly in the truststore worked. |
Restricting protocols and cipher suites
Tika does not set TLS protocol or cipher suite policy; the JVM does. By default the server offers what the JVM enables (TLS 1.2 and 1.3 on current JDKs), minus Jetty’s weak-suite exclusions. To restrict it, set the JVM’s own properties when starting the server — for example, TLS 1.3 only:
java -Djdk.tls.server.protocols=TLSv1.3 -jar tika-server-standard.jar -c tika-config.json
An explicit allowlist of cipher suites:
java -Djdk.tls.server.cipherSuites=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 \
-jar tika-server-standard.jar -c tika-config.json
In a container, put the flags in JAVA_TOOL_OPTIONS. The jdk.tls.server.* properties apply
only where Java is the TLS server, so outbound HTTPS from fetchers and parsers is unaffected. The
reverse does not hold: jdk.tls.client.protocols restricts the server too, since Jetty starts
from the JVM’s client-side protocol defaults, so the server offers only protocols both properties
allow. To ban an algorithm everywhere, client side included, edit jdk.tls.disabledAlgorithms in
the JDK’s java.security.
Jetty’s weak-suite exclusions apply on top of jdk.tls.server.cipherSuites: a listed suite whose
name ends in _SHA, _SHA1 or _MD5, or starts with TLS_RSA_ or SSL_, or contains
_NULL_ or _anon_, is dropped without a warning.
Through 4.1, tlsConfig had includedProtocols, excludedProtocols,
includedCipherSuites and excludedCipherSuites. None of them worked as documented:
includedProtocols did not stop other versions being offered, excludedCipherSuites on its own
disabled every suite, and includedCipherSuites could be replaced by the JVM defaults. They were
removed in 4.2.0, and a config that sets any of them is refused with the property to use instead.
clientAuthenticationWanted was removed too: tika-server never reads client certificates, so
asking for one without requiring it authenticated nobody. true is refused; false is ignored
with a warning. Use clientAuthenticationRequired.
|
Certificate Expiration Warnings
At startup, Tika Server checks every certificate in the configured stores and logs:
-
ERROR — already expired, or not yet valid; TLS will fail.
-
WARN — expires within
certExpirationWarningDays(default 30). -
DEBUG — valid.
WARN TlsConfig - Certificate 'server' in keystore expires in 15 days on Sat Feb 15 12:00:00 UTC 2026. Consider renewing soon.
ERROR TlsConfig - Certificate 'server' in keystore has EXPIRED on Mon Jan 01 12:00:00 UTC 2026. TLS connections will fail!
Set "certExpirationWarningDays": 60 to warn earlier, or 0 to disable the check entirely.
Generating Test Certificates
Self-signed certificates for testing only. In production use certificates from a trusted CA.
# Server key pair, and its certificate for clients to trust
keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -validity 365 \
-keystore server-keystore.p12 -storetype PKCS12 -storepass changeit \
-dname "CN=localhost,OU=Tika,O=Apache,L=Unknown,ST=Unknown,C=US"
keytool -exportcert -alias server -keystore server-keystore.p12 \
-storetype PKCS12 -storepass changeit -file server.crt
# Client key pair (2-way TLS only), and its certificate for the server to trust
keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -validity 365 \
-keystore client-keystore.p12 -storetype PKCS12 -storepass changeit \
-dname "CN=TikaClient,OU=Tika,O=Apache,L=Unknown,ST=Unknown,C=US"
keytool -exportcert -alias client -keystore client-keystore.p12 \
-storetype PKCS12 -storepass changeit -file client.crt
# Truststores: the server trusts the client cert, the client trusts the server cert
keytool -importcert -alias client -file client.crt -noprompt \
-keystore server-truststore.p12 -storetype PKCS12 -storepass changeit
keytool -importcert -alias server -file server.crt -noprompt \
-keystore client-truststore.p12 -storetype PKCS12 -storepass changeit
Testing
# 1-way TLS: trust the server certificate
curl --cacert server.crt https://localhost:9998/tika
# 2-way TLS: also present a client certificate
curl --cacert server.crt --cert client.crt --key client-key.pem \
https://localhost:9998/tika
# Self-signed, throwaway testing only -- skips verification entirely
curl -k https://localhost:9998/tika
From Java:
// 2-way TLS
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(
new File("client-keystore.p12"),
"changeit".toCharArray(),
"changeit".toCharArray())
.loadTrustMaterial(
new File("client-truststore.p12"),
"changeit".toCharArray())
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
HttpResponse response = client.execute(new HttpGet("https://localhost:9998/tika"));
Containers
Mount the config file and the keystores into the container, and point -c at the config. The
paths in tlsConfig are container paths, not host paths.
docker run -d --name tika-server -p 9998:9998 \
-e KEYSTORE_PASSWORD -e TRUSTSTORE_PASSWORD \
-v $(pwd)/config:/tika/config:ro \
-v $(pwd)/certs:/tika/certs:ro \
apache/tika:<version> \
-c /tika/config/tika-config.json
With /tika/config/tika-config.json holding, for example:
{
"server": {
"port": 9998,
"tlsConfig": {
"keyStoreType": "PKCS12",
"keyStoreFile": "/tika/certs/server-keystore.p12",
"keyStorePassword": "${env:KEYSTORE_PASSWORD}",
"trustStoreType": "PKCS12",
"trustStoreFile": "/tika/certs/server-truststore.p12",
"trustStorePassword": "${env:TRUSTSTORE_PASSWORD}",
"clientAuthenticationRequired": true
}
}
}
The official image’s entrypoint already passes -h 0.0.0.0, so the container listens on all
interfaces regardless of server.host.
|
Docker Compose
services:
tika:
image: apache/tika:latest
ports:
- "9998:9998"
volumes:
- ./config:/tika/config:ro
- ./certs:/tika/certs:ro
command: ["-c", "/tika/config/tika-config.json"]
environment:
KEYSTORE_PASSWORD: ${KEYSTORE_PASSWORD}
TRUSTSTORE_PASSWORD: ${TRUSTSTORE_PASSWORD}
healthcheck:
test: ["CMD", "curl", "-f", "--cacert", "/tika/certs/server.crt", "https://localhost:9998/tika"]
interval: 30s
timeout: 10s
retries: 3
Kubernetes
kubectl create secret generic tika-tls-certs \
--from-file=server-keystore.p12=./certs/server-keystore.p12 \
--from-file=server-truststore.p12=./certs/server-truststore.p12 \
--from-literal=keystore-password="$KEYSTORE_PASSWORD" \
--from-literal=truststore-password="$TRUSTSTORE_PASSWORD"
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tika-server
spec:
replicas: 1
selector:
matchLabels:
app: tika
template:
metadata:
labels:
app: tika
spec:
containers:
- name: tika
image: apache/tika:latest
ports:
- containerPort: 9998
args: ["-c", "/tika/config/tika-config.json"]
env:
- name: KEYSTORE_PASSWORD
valueFrom:
secretKeyRef: {name: tika-tls-certs, key: keystore-password}
- name: TRUSTSTORE_PASSWORD
valueFrom:
secretKeyRef: {name: tika-tls-certs, key: truststore-password}
volumeMounts:
- name: config
mountPath: /tika/config
readOnly: true
- name: certs
mountPath: /tika/certs
readOnly: true
volumes:
- name: config
secret:
secretName: tika-config
- name: certs
secret:
secretName: tika-tls-certs
Keystore passwords
Write the passwords as ${env:NAME} (as in the examples above) and pass them in the environment,
so the config file holds no secret. Tika expands ${env:NAME} anywhere in the startup config; a
variable that is not set stops startup
(Secrets from the environment). Nothing else is expanded: there
is no form that reads a file such as /run/secrets/…. For a secret mounted as a file, export it
into the environment in your own entrypoint script.
Troubleshooting
| Message | Cause |
|---|---|
|
A |
|
The deprecated |
|
A 4.1 config with |
|
Use |
|
Wrong path. Absolute paths are safest; relative ones resolve against the working directory, which inside a container is not where you think it is. |
|
Some but not all of |
|
The server refused a client’s certificate; the client sees |
|
|
SSL handshake failure |
Expired or invalid certificate; the client does not trust the server’s certificate (1-way); the server does not trust the client’s (2-way); or no protocol/cipher suite in common. |
For handshake detail, add -Djavax.net.debug=ssl:handshake to the JVM:
java -Djavax.net.debug=ssl:handshake -jar tika-server-standard-X.Y.Z.jar -c config.json
Security Best Practices
-
Use strong keystore and truststore passwords, and never commit them.
-
Stay on TLS 1.2/1.3 (the default) and restrict cipher suites to modern algorithms.
-
Prefer PKCS12 over JKS.
-
Use CA-issued certificates in production and rotate them before expiry.
-
Use 2-way TLS when clients need to be authenticated.
-
Protect keystore files with restrictive filesystem permissions.
See Also
-
Security — general security considerations
-
Robustness — process isolation and fault tolerance