You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2026/01/31 23:03:20 UTC

(tika) branch TIKA-4642 created (now f2f324d8d2)

This is an automated email from the ASF dual-hosted git repository.

tallison pushed a change to branch TIKA-4642
in repository https://gitbox.apache.org/repos/asf/tika.git


      at f2f324d8d2 TIKA-4642 - improve tls configuration and documentation

This branch includes the following new commits:

     new f2f324d8d2 TIKA-4642 - improve tls configuration and documentation

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(tika) 01/01: TIKA-4642 - improve tls configuration and documentation

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tallison pushed a commit to branch TIKA-4642
in repository https://gitbox.apache.org/repos/asf/tika.git

commit f2f324d8d2c3185e1232cc23249a136eff43e273
Author: tallison <ta...@apache.org>
AuthorDate: Sat Jan 31 18:03:07 2026 -0500

    TIKA-4642 - improve tls configuration and documentation
---
 docs/modules/ROOT/nav.adoc                         |   1 +
 .../ROOT/pages/using-tika/server/index.adoc        |   6 +-
 docs/modules/ROOT/pages/using-tika/server/tls.adoc | 651 +++++++++++++++++++++
 .../apache/tika/server/core/TikaServerProcess.java |  25 +
 .../org/apache/tika/server/core/TlsConfig.java     | 229 +++++++-
 5 files changed, 892 insertions(+), 20 deletions(-)

diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index ef0165c69b..8cf1e26a0a 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -17,6 +17,7 @@
 * xref:using-tika/index.adoc[Using Tika]
 ** xref:using-tika/java-api/index.adoc[Java API]
 ** xref:using-tika/server/index.adoc[Tika Server]
+*** xref:using-tika/server/tls.adoc[TLS/SSL Configuration]
 ** xref:using-tika/cli/index.adoc[Command Line]
 ** xref:using-tika/grpc/index.adoc[gRPC]
 * xref:pipes/index.adoc[Pipes]
diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc b/docs/modules/ROOT/pages/using-tika/server/index.adoc
index accfc02700..59017b943b 100644
--- a/docs/modules/ROOT/pages/using-tika/server/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc
@@ -35,8 +35,4 @@ The server starts on port 9998 by default.
 
 == Topics
 
-// Add links to specific topics as they are created
-// * link:installation.html[Installation]
-// * link:endpoints.html[REST Endpoints]
-// * link:configuration.html[Configuration]
-// * link:docker.html[Docker Deployment]
+* xref:using-tika/server/tls.adoc[TLS/SSL Configuration] - Secure your server with TLS and mutual authentication
diff --git a/docs/modules/ROOT/pages/using-tika/server/tls.adoc b/docs/modules/ROOT/pages/using-tika/server/tls.adoc
new file mode 100644
index 0000000000..8823b9a4f5
--- /dev/null
+++ b/docs/modules/ROOT/pages/using-tika/server/tls.adoc
@@ -0,0 +1,651 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+= TLS/SSL Configuration
+:toc:
+:toclevels: 3
+
+Tika Server supports TLS (Transport Layer Security) for encrypted communication
+and mutual authentication (2-way TLS / mTLS).
+
+== Choosing a Security Approach
+
+For most deployments, we recommend placing Tika Server behind a **reverse proxy** (such as nginx, Apache httpd, or Traefik) that handles TLS termination. This approach simplifies certificate management, enables additional features like rate limiting and authentication, and follows a well-established pattern with extensive documentation. In this setup, the reverse proxy handles HTTPS connections from clients, and Tika Server runs on plain HTTP in a private network or on localhost. If you  [...]
+
+Direct TLS configuration on Tika Server (described in this document) is useful when you cannot use a reverse proxy, need client certificate authentication (2-way TLS/mTLS), or prefer a simpler single-process deployment. If you only need to restrict access without TLS, consider network-level controls such as binding to localhost, firewall rules, or running in a private subnet.
+
+== Overview
+
+TLS configuration options:
+
+* **1-Way TLS**: Server presents its certificate; clients verify the server
+* **2-Way TLS (mTLS)**: Both server and client present certificates for mutual authentication
+
+== Quick Start
+
+=== 1-Way TLS
+
+Server authenticates to clients, but clients are not required to present certificates.
+
+[source,json]
+----
+{
+  "server": {
+    "host": "localhost",
+    "port": 9998
+  },
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "clientAuthenticationWanted": false,
+    "clientAuthenticationRequired": false
+  }
+}
+----
+
+=== 2-Way TLS (mTLS)
+
+Both server and client must present valid certificates.
+
+[source,json]
+----
+{
+  "server": {
+    "host": "localhost",
+    "port": 9998
+  },
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "trustStoreType": "PKCS12",
+    "trustStoreFile": "/path/to/server-truststore.p12",
+    "trustStorePassword": "your-password",
+    "clientAuthenticationWanted": true,
+    "clientAuthenticationRequired": true
+  }
+}
+----
+
+== Docker Deployment
+
+When running Tika Server in Docker with TLS, you need to:
+
+1. Create a configuration file with TLS settings
+2. Mount the configuration file into the container
+3. Mount the keystore (and truststore for 2-way TLS) into the container
+4. Start the container with the `-c` flag pointing to the config file
+
+=== Directory Structure
+
+Create a directory structure on your host machine:
+
+[source]
+----
+tika-tls/
+├── config/
+│   └── tika-config.json
+└── certs/
+    ├── server-keystore.p12
+    └── server-truststore.p12    # (for 2-way TLS)
+----
+
+=== Configuration File
+
+Create `tika-tls/config/tika-config.json`:
+
+[source,json]
+----
+{
+  "server": {
+    "host": "0.0.0.0",
+    "port": 9998
+  },
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/tika/certs/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "trustStoreType": "PKCS12",
+    "trustStoreFile": "/tika/certs/server-truststore.p12",
+    "trustStorePassword": "your-password",
+    "clientAuthenticationRequired": true
+  }
+}
+----
+
+NOTE: Use `0.0.0.0` as the host to bind to all interfaces inside the container.
+The paths (`/tika/certs/...`) must match where you mount the files in the container.
+
+=== Running with Docker
+
+[source,bash]
+----
+docker run -d \
+  --name tika-server \
+  -p 9998:9998 \
+  -v $(pwd)/tika-tls/config:/tika/config:ro \
+  -v $(pwd)/tika-tls/certs:/tika/certs:ro \
+  apache/tika:<version> \
+  -c /tika/config/tika-config.json
+----
+
+Options explained:
+
+* `-p 9998:9998` - Expose the HTTPS port
+* `-v .../config:/tika/config:ro` - Mount config directory (read-only)
+* `-v .../certs:/tika/certs:ro` - Mount certificates directory (read-only)
+* `-c /tika/config/tika-config.json` - Path to config file inside container
+
+=== Docker Compose
+
+For more complex deployments, use Docker Compose:
+
+[source,yaml]
+----
+# docker-compose.yml
+version: '3.8'
+
+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"]
+    healthcheck:
+      test: ["CMD", "curl", "-f", "--cacert", "/tika/certs/server.crt", "https://localhost:9998/tika"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+----
+
+Run with:
+
+[source,bash]
+----
+docker-compose up -d
+----
+
+=== Using Secrets (Docker Swarm / Kubernetes)
+
+For production deployments, avoid storing passwords in plain text config files.
+
+==== Docker Swarm
+
+Use Docker secrets for sensitive data:
+
+[source,bash]
+----
+# Create secrets
+echo "your-keystore-password" | docker secret create tika_keystore_password -
+echo "your-truststore-password" | docker secret create tika_truststore_password -
+
+# Reference in docker-compose.yml (swarm mode)
+services:
+  tika:
+    image: apache/tika:latest
+    secrets:
+      - tika_keystore_password
+      - tika_truststore_password
+    # Read password from /run/secrets/tika_keystore_password in entrypoint script
+----
+
+==== Kubernetes
+
+Use Kubernetes secrets:
+
+[source,yaml]
+----
+# Create secret for certificates
+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
+
+# Create secret for passwords
+kubectl create secret generic tika-tls-passwords \
+  --from-literal=keystore-password=your-password \
+  --from-literal=truststore-password=your-password
+----
+
+[source,yaml]
+----
+# 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"]
+        volumeMounts:
+        - name: config
+          mountPath: /tika/config
+          readOnly: true
+        - name: certs
+          mountPath: /tika/certs
+          readOnly: true
+        env:
+        - name: KEYSTORE_PASSWORD
+          valueFrom:
+            secretKeyRef:
+              name: tika-tls-passwords
+              key: keystore-password
+      volumes:
+      - name: config
+        configMap:
+          name: tika-config
+      - name: certs
+        secret:
+          secretName: tika-tls-certs
+----
+
+=== Testing Docker TLS Setup
+
+Test that TLS is working:
+
+[source,bash]
+----
+# 1-way TLS (trust server certificate)
+curl --cacert ./certs/server.crt https://localhost:9998/tika
+
+# 2-way TLS (provide client certificate)
+curl --cacert ./certs/server.crt \
+     --cert ./certs/client.crt \
+     --key ./certs/client-key.pem \
+     https://localhost:9998/tika
+----
+
+If using self-signed certificates and just testing:
+
+[source,bash]
+----
+# Skip certificate verification (NOT for production!)
+curl -k https://localhost:9998/tika
+----
+
+== Configuration Reference
+
+=== tlsConfig Properties
+
+[cols="2,1,1,4"]
+|===
+|Property |Type |Default |Description
+
+|`active`
+|boolean
+|false
+|Enable TLS. When true, the server uses HTTPS.
+
+|`keyStoreType`
+|string
+|null
+|Keystore format: `PKCS12` (recommended) or `JKS`.
+
+|`keyStoreFile`
+|string
+|null
+|Path to the server's keystore file containing its private key and certificate.
+
+|`keyStorePassword`
+|string
+|null
+|Password for the keystore.
+
+|`trustStoreType`
+|string
+|null
+|Truststore format: `PKCS12` (recommended) or `JKS`.
+
+|`trustStoreFile`
+|string
+|null
+|Path to the truststore containing trusted client certificates (for 2-way TLS).
+
+|`trustStorePassword`
+|string
+|null
+|Password for the truststore.
+
+|`clientAuthenticationWanted`
+|boolean
+|false
+|Request client certificates but don't require them. Clients without certificates can still connect.
+
+|`clientAuthenticationRequired`
+|boolean
+|false
+|Require client certificates. Clients must present a valid certificate trusted by the server.
+
+|`includedProtocols`
+|list
+|["TLSv1.2", "TLSv1.3"]
+|TLS protocol versions to enable. Default only allows TLS 1.2 and 1.3.
+
+|`excludedProtocols`
+|list
+|null
+|TLS protocol versions to explicitly disable.
+
+|`includedCipherSuites`
+|list
+|null
+|Cipher suites to enable. If null, uses JVM defaults.
+
+|`excludedCipherSuites`
+|list
+|null
+|Cipher suites to disable.
+
+|`certExpirationWarningDays`
+|integer
+|30
+|Days before certificate expiration to log warnings. Set to 0 to disable.
+|===
+
+== Certificate Expiration Warnings
+
+Tika Server automatically checks certificate expiration at startup and logs warnings
+for certificates that will expire soon. This helps prevent unexpected outages due to
+expired certificates.
+
+=== Warning Levels
+
+* **ERROR**: Certificate has already expired or is not yet valid - TLS will fail
+* **WARN**: Certificate expires within the configured threshold (default: 30 days)
+* **DEBUG**: Certificate is valid (logged at debug level)
+
+=== Example Log Output
+
+[source]
+----
+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!
+----
+
+=== Configuring the Warning Threshold
+
+To change when warnings are logged (default is 30 days before expiration):
+
+[source,json]
+----
+{
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "certExpirationWarningDays": 60
+  }
+}
+----
+
+Set to `0` to disable expiration checking.
+
+== Generating Certificates
+
+This section shows how to generate self-signed certificates for testing.
+For production, use certificates from a trusted Certificate Authority (CA).
+
+=== Generate Server Keystore
+
+[source,bash]
+----
+# Generate server private key and certificate
+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"
+
+# Export server certificate (for clients to trust)
+keytool -exportcert \
+  -alias server \
+  -keystore server-keystore.p12 \
+  -storetype PKCS12 \
+  -storepass changeit \
+  -file server.crt
+----
+
+=== Generate Client Keystore (for 2-Way TLS)
+
+[source,bash]
+----
+# Generate client private key and certificate
+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"
+
+# Export client certificate (for server to trust)
+keytool -exportcert \
+  -alias client \
+  -keystore client-keystore.p12 \
+  -storetype PKCS12 \
+  -storepass changeit \
+  -file client.crt
+----
+
+=== Create Truststores
+
+[source,bash]
+----
+# Create server truststore (import client certificate)
+keytool -importcert \
+  -alias client \
+  -file client.crt \
+  -keystore server-truststore.p12 \
+  -storetype PKCS12 \
+  -storepass changeit \
+  -noprompt
+
+# Create client truststore (import server certificate)
+keytool -importcert \
+  -alias server \
+  -file server.crt \
+  -keystore client-truststore.p12 \
+  -storetype PKCS12 \
+  -storepass changeit \
+  -noprompt
+----
+
+== Protocol and Cipher Configuration
+
+=== Restricting TLS Versions
+
+By default, only TLS 1.2 and TLS 1.3 are enabled. TLS 1.0 and 1.1 are considered
+insecure and are not enabled by default.
+
+To use only TLS 1.3:
+
+[source,json]
+----
+{
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "includedProtocols": ["TLSv1.3"]
+  }
+}
+----
+
+=== Restricting Cipher Suites
+
+To allow only specific cipher suites:
+
+[source,json]
+----
+{
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "includedCipherSuites": [
+      "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"
+    ]
+  }
+}
+----
+
+To exclude weak cipher suites:
+
+[source,json]
+----
+{
+  "tlsConfig": {
+    "active": true,
+    "keyStoreType": "PKCS12",
+    "keyStoreFile": "/path/to/server-keystore.p12",
+    "keyStorePassword": "your-password",
+    "excludedCipherSuites": [
+      ".*CBC.*",
+      ".*RC4.*",
+      ".*3DES.*",
+      ".*NULL.*"
+    ]
+  }
+}
+----
+
+== Testing TLS Configuration
+
+=== Test with curl (1-Way TLS)
+
+[source,bash]
+----
+# Trust the server certificate
+curl --cacert server.crt https://localhost:9998/tika
+----
+
+=== Test with curl (2-Way TLS)
+
+[source,bash]
+----
+# Provide client certificate and trust server certificate
+curl --cacert server.crt \
+     --cert client.crt \
+     --key client-key.pem \
+     https://localhost:9998/tika
+----
+
+=== Test with Java Client
+
+[source,java]
+----
+// Configure SSL context for 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();
+
+HttpGet request = new HttpGet("https://localhost:9998/tika");
+HttpResponse response = client.execute(request);
+----
+
+== Security Best Practices
+
+1. **Use strong passwords** for keystores and truststores
+2. **Use TLS 1.2 or 1.3** only (default configuration)
+3. **Prefer PKCS12** format over JKS (better interoperability)
+4. **Use certificates from a trusted CA** in production
+5. **Rotate certificates** before they expire
+6. **Restrict cipher suites** to strong, modern algorithms
+7. **Use 2-way TLS** when clients need to be authenticated
+8. **Protect keystore files** with appropriate file system permissions
+9. **Never commit passwords** to version control; use environment variables or secrets management
+
+== Troubleshooting
+
+=== Common Errors
+
+**"keyStoreFile does not exist or is not a file"**
+
+The specified keystore file path is incorrect or the file doesn't exist.
+Verify the path is absolute or relative to the working directory.
+
+**"Partial truststore configuration detected"**
+
+You've set some but not all truststore properties. Either set all three
+(`trustStoreType`, `trustStoreFile`, `trustStorePassword`) or none.
+
+**"requiring client authentication, but no trust store has been specified"**
+
+You've set `clientAuthenticationRequired: true` but haven't configured a truststore.
+The server needs a truststore to verify client certificates.
+
+**SSL handshake failures**
+
+- Verify certificates are valid and not expired
+- Ensure client trusts server certificate (for 1-way TLS)
+- Ensure server trusts client certificate (for 2-way TLS)
+- Check that protocol versions and cipher suites are compatible
+
+=== Enable SSL Debug Logging
+
+Add this JVM argument to see detailed SSL handshake information:
+
+[source,bash]
+----
+java -Djavax.net.debug=ssl:handshake -jar tika-server-standard.jar -c config.json
+----
+
+== See Also
+
+* xref:security.adoc[Security] - General security considerations
+* xref:advanced/robustness.adoc[Robustness] - Process isolation and fault tolerance
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
index 4edf08bc9a..23e44852ea 100644
--- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java
@@ -38,6 +38,7 @@ import org.apache.cxf.binding.BindingFactoryManager;
 import org.apache.cxf.configuration.jsse.TLSParameterJaxBUtils;
 import org.apache.cxf.configuration.jsse.TLSServerParameters;
 import org.apache.cxf.configuration.security.ClientAuthentication;
+import org.apache.cxf.configuration.security.FiltersType;
 import org.apache.cxf.configuration.security.KeyManagersType;
 import org.apache.cxf.configuration.security.KeyStoreType;
 import org.apache.cxf.configuration.security.TrustManagersType;
@@ -209,6 +210,8 @@ public class TikaServerProcess {
                 .getTlsConfig()
                 .isActive()) {
             LOG.warn("The TLS configuration is in BETA and might change " + "dramatically in future releases.");
+            // Check for expiring certificates and log warnings
+            tikaServerConfig.getTlsConfig().checkCertificateExpiration();
             TLSServerParameters tlsParams = getTlsParams(tikaServerConfig.getTlsConfig());
             JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
             factory.setBus(sf.getBus());
@@ -250,6 +253,28 @@ public class TikaServerProcess {
         clientAuthentication.setRequired(tlsConfig.isClientAuthenticationRequired());
         clientAuthentication.setWant(tlsConfig.isClientAuthenticationWanted());
         parameters.setClientAuthentication(clientAuthentication);
+
+        // Configure TLS protocols
+        if (tlsConfig.getIncludedProtocols() != null && !tlsConfig.getIncludedProtocols().isEmpty()) {
+            parameters.setIncludeProtocols(tlsConfig.getIncludedProtocols());
+        }
+        if (tlsConfig.getExcludedProtocols() != null && !tlsConfig.getExcludedProtocols().isEmpty()) {
+            parameters.setExcludeProtocols(tlsConfig.getExcludedProtocols());
+        }
+
+        // Configure cipher suites
+        if ((tlsConfig.getIncludedCipherSuites() != null && !tlsConfig.getIncludedCipherSuites().isEmpty()) ||
+                (tlsConfig.getExcludedCipherSuites() != null && !tlsConfig.getExcludedCipherSuites().isEmpty())) {
+            FiltersType cipherSuitesFilter = new FiltersType();
+            if (tlsConfig.getIncludedCipherSuites() != null) {
+                cipherSuitesFilter.getInclude().addAll(tlsConfig.getIncludedCipherSuites());
+            }
+            if (tlsConfig.getExcludedCipherSuites() != null) {
+                cipherSuitesFilter.getExclude().addAll(tlsConfig.getExcludedCipherSuites());
+            }
+            parameters.setCipherSuitesFilter(cipherSuitesFilter);
+        }
+
         return parameters;
     }
 
diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
index c0adb23bd5..5fbfb43820 100644
--- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
+++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TlsConfig.java
@@ -16,14 +16,45 @@
  */
 package org.apache.tika.server.core;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.tika.exception.TikaConfigException;
 import org.apache.tika.utils.StringUtils;
 
 public class TlsConfig {
 
-    //TODO make this configurable
-    private final boolean passwordsAESEncrypted = false;
+    private static final Logger LOG = LoggerFactory.getLogger(TlsConfig.class);
+
+    /**
+     * Default TLS protocols - only TLS 1.2 and 1.3 are enabled by default.
+     * TLS 1.0 and 1.1 are considered insecure and should not be used.
+     */
+    public static final List<String> DEFAULT_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.3");
+
+    /**
+     * Default warning threshold for certificate expiration (30 days).
+     */
+    public static final int DEFAULT_CERT_EXPIRATION_WARNING_DAYS = 30;
+
     private boolean active = false;
+    private int certExpirationWarningDays = DEFAULT_CERT_EXPIRATION_WARNING_DAYS;
     private String keyStoreType = null;
     private String keyStorePassword = null;
     private String keyStoreFile = null;
@@ -32,9 +63,16 @@ public class TlsConfig {
     private String trustStoreFile = null;
 
     private boolean clientAuthenticationWanted = false;
-
     private boolean clientAuthenticationRequired = false;
 
+    // TLS protocol configuration
+    private List<String> includedProtocols = DEFAULT_PROTOCOLS;
+    private List<String> excludedProtocols = null;
+
+    // Cipher suite configuration
+    private List<String> includedCipherSuites = null;
+    private List<String> excludedCipherSuites = null;
+
     public boolean isActive() {
         return active;
     }
@@ -43,10 +81,6 @@ public class TlsConfig {
         this.active = active;
     }
 
-    public boolean isPasswordsAESEncrypted() {
-        return passwordsAESEncrypted;
-    }
-
     public String getKeyStoreType() {
         return keyStoreType;
     }
@@ -97,6 +131,7 @@ public class TlsConfig {
 
     public void checkInitialization() throws TikaConfigException {
         if (active) {
+            // Validate keystore configuration
             if (StringUtils.isBlank(keyStoreType)) {
                 throw new TikaConfigException("must initialize keyStoreType");
             } else if (StringUtils.isBlank(keyStoreFile)) {
@@ -104,21 +139,59 @@ public class TlsConfig {
             } else if (StringUtils.isBlank(keyStorePassword)) {
                 throw new TikaConfigException("must initialize keyStorePassword");
             }
+
+            // Validate keystore file exists
+            File ksFile = new File(keyStoreFile);
+            if (!ksFile.isFile()) {
+                throw new TikaConfigException("keyStoreFile does not exist or is not a file: " + keyStoreFile);
+            }
+
+            // Validate truststore configuration
             if (hasTrustStore()) {
                 if (StringUtils.isBlank(trustStoreType)) {
-                    throw new TikaConfigException("must initialize trustStoreType " + "if there's any trustStore info");
+                    throw new TikaConfigException("must initialize trustStoreType if there's any trustStore info");
                 } else if (StringUtils.isBlank(trustStoreFile)) {
-                    throw new TikaConfigException("must initialize trustStoreFile " + "if there's any trustStore info");
+                    throw new TikaConfigException("must initialize trustStoreFile if there's any trustStore info");
                 } else if (StringUtils.isBlank(trustStorePassword)) {
-                    throw new TikaConfigException("must initialize trustStorePassword " + "if there's any trustStore info");
+                    throw new TikaConfigException("must initialize trustStorePassword if there's any trustStore info");
+                }
+
+                // Validate truststore file exists
+                File tsFile = new File(trustStoreFile);
+                if (!tsFile.isFile()) {
+                    throw new TikaConfigException("trustStoreFile does not exist or is not a file: " + trustStoreFile);
                 }
             }
+
+            // Warn about partial truststore configuration
+            checkPartialTrustStoreConfig();
+
             if (!hasTrustStore() && isClientAuthenticationRequired()) {
-                throw new TikaConfigException("requiring client authentication, but no trust " + "store has been specified?!");
+                throw new TikaConfigException("requiring client authentication, but no trust store has been specified");
             }
         }
     }
 
+    /**
+     * Check for partial truststore configuration and throw an exception.
+     * If only some truststore fields are set, this is likely a configuration error.
+     */
+    private void checkPartialTrustStoreConfig() throws TikaConfigException {
+        boolean hasType = !StringUtils.isBlank(trustStoreType);
+        boolean hasFile = !StringUtils.isBlank(trustStoreFile);
+        boolean hasPassword = !StringUtils.isBlank(trustStorePassword);
+
+        // If any field is set but not all, that's a configuration error
+        int setCount = (hasType ? 1 : 0) + (hasFile ? 1 : 0) + (hasPassword ? 1 : 0);
+        if (setCount > 0 && setCount < 3) {
+            StringBuilder missing = new StringBuilder("Partial truststore configuration detected. Missing: ");
+            if (!hasType) missing.append("trustStoreType ");
+            if (!hasFile) missing.append("trustStoreFile ");
+            if (!hasPassword) missing.append("trustStorePassword ");
+            throw new TikaConfigException(missing.toString().trim());
+        }
+    }
+
     public boolean isClientAuthenticationWanted() {
         return clientAuthenticationWanted;
     }
@@ -135,12 +208,138 @@ public class TlsConfig {
         this.clientAuthenticationRequired = clientAuthenticationRequired;
     }
 
+    public List<String> getIncludedProtocols() {
+        return includedProtocols;
+    }
+
+    public void setIncludedProtocols(List<String> includedProtocols) {
+        this.includedProtocols = includedProtocols;
+    }
+
+    public List<String> getExcludedProtocols() {
+        return excludedProtocols;
+    }
+
+    public void setExcludedProtocols(List<String> excludedProtocols) {
+        this.excludedProtocols = excludedProtocols;
+    }
+
+    public List<String> getIncludedCipherSuites() {
+        return includedCipherSuites;
+    }
+
+    public void setIncludedCipherSuites(List<String> includedCipherSuites) {
+        this.includedCipherSuites = includedCipherSuites;
+    }
+
+    public List<String> getExcludedCipherSuites() {
+        return excludedCipherSuites;
+    }
+
+    public void setExcludedCipherSuites(List<String> excludedCipherSuites) {
+        this.excludedCipherSuites = excludedCipherSuites;
+    }
+
+    public int getCertExpirationWarningDays() {
+        return certExpirationWarningDays;
+    }
+
+    public void setCertExpirationWarningDays(int certExpirationWarningDays) {
+        this.certExpirationWarningDays = certExpirationWarningDays;
+    }
+
+    /**
+     * Check certificate expiration dates and log warnings for certificates
+     * expiring within the configured threshold.
+     * <p>
+     * This method should be called after {@link #checkInitialization()} to
+     * warn about upcoming certificate expirations.
+     */
+    public void checkCertificateExpiration() {
+        if (!active || certExpirationWarningDays <= 0) {
+            return;
+        }
+
+        Instant warningThreshold = Instant.now().plus(Duration.ofDays(certExpirationWarningDays));
+
+        // Check keystore certificates
+        if (!StringUtils.isBlank(keyStoreFile)) {
+            checkKeystoreExpiration(keyStoreFile, keyStoreType, keyStorePassword, "keystore", warningThreshold);
+        }
+
+        // Check truststore certificates
+        if (hasTrustStore()) {
+            checkKeystoreExpiration(trustStoreFile, trustStoreType, trustStorePassword, "truststore", warningThreshold);
+        }
+    }
+
+    private void checkKeystoreExpiration(String file, String type, String password,
+                                          String storeName, Instant warningThreshold) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            KeyStore keyStore = KeyStore.getInstance(type);
+            keyStore.load(fis, password.toCharArray());
+
+            Enumeration<String> aliases = keyStore.aliases();
+            while (aliases.hasMoreElements()) {
+                String alias = aliases.nextElement();
+                Certificate cert = keyStore.getCertificate(alias);
+
+                if (cert instanceof X509Certificate) {
+                    X509Certificate x509 = (X509Certificate) cert;
+                    Date notAfter = x509.getNotAfter();
+                    Date notBefore = x509.getNotBefore();
+                    Instant now = Instant.now();
+
+                    // Check if already expired
+                    if (notAfter.toInstant().isBefore(now)) {
+                        LOG.error("Certificate '{}' in {} has EXPIRED on {}. " +
+                                        "TLS connections will fail!",
+                                alias, storeName, notAfter);
+                    }
+                    // Check if not yet valid
+                    else if (notBefore.toInstant().isAfter(now)) {
+                        LOG.error("Certificate '{}' in {} is not yet valid until {}. " +
+                                        "TLS connections will fail!",
+                                alias, storeName, notBefore);
+                    }
+                    // Check if expiring soon
+                    else if (notAfter.toInstant().isBefore(warningThreshold)) {
+                        long daysUntilExpiry = Duration.between(now, notAfter.toInstant()).toDays();
+                        LOG.warn("Certificate '{}' in {} expires in {} days on {}. " +
+                                        "Consider renewing soon.",
+                                alias, storeName, daysUntilExpiry, notAfter);
+                    } else {
+                        LOG.debug("Certificate '{}' in {} is valid until {}",
+                                alias, storeName, notAfter);
+                    }
+                }
+            }
+        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
+            LOG.warn("Unable to check certificate expiration for {}: {}", storeName, e.getMessage());
+        }
+    }
+
     @Override
     public String toString() {
-        return "TlsConfig{" + "active=" + active + ", passwordsAESEncrypted=" + passwordsAESEncrypted + ", keyStoreType='" + keyStoreType + '\'' + ", keyStorePassword='" +
-                keyStorePassword + '\'' + ", keyStoreFile='" + keyStoreFile + '\'' + ", trustStoreType='" + trustStoreType + '\'' + ", trustStorePassword='" + trustStorePassword +
-                '\'' + ", trustStoreFile='" + trustStoreFile + '\'' + ", clientAuthenticationWanted=" + clientAuthenticationWanted + ", isClientAuthenticationRequired=" +
-                clientAuthenticationRequired + '}';
+        return "TlsConfig{" +
+                "active=" + active +
+                ", keyStoreType='" + keyStoreType + '\'' +
+                ", keyStorePassword='" + maskPassword(keyStorePassword) + '\'' +
+                ", keyStoreFile='" + keyStoreFile + '\'' +
+                ", trustStoreType='" + trustStoreType + '\'' +
+                ", trustStorePassword='" + maskPassword(trustStorePassword) + '\'' +
+                ", trustStoreFile='" + trustStoreFile + '\'' +
+                ", clientAuthenticationWanted=" + clientAuthenticationWanted +
+                ", clientAuthenticationRequired=" + clientAuthenticationRequired +
+                ", includedProtocols=" + includedProtocols +
+                ", excludedProtocols=" + excludedProtocols +
+                ", includedCipherSuites=" + includedCipherSuites +
+                ", excludedCipherSuites=" + excludedCipherSuites +
+                '}';
+    }
+
+    private static String maskPassword(String password) {
+        return password == null ? "null" : "****";
     }
 
     public boolean hasTrustStore() {