If you are developing Java applications on Azure Functions (Linux dedicated plan) and need to connect to services secured by self-signed certificates, you have likely encountered the dreaded SSL handshake error:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
By default, the Java Virtual Machine (JVM) only trusts certificates signed by well-known Certificate Authorities (CAs). To fix this, you need to tell your Java Function App to trust your specific self-signed certificate.
While there are several ways to achieve this, this guide outlines the best practice: manually adding the certificate to a custom Java keystore located in persistent storage.
Why this approach?
In Azure App Service and Azure Functions (Linux), the file system is generally ephemeral, meaning changes to system folders (like /usr/lib/jvm) are lost upon restart. However, the /home directory is persistent.
By creating a custom truststore in /home and pointing the JVM to it, your configuration remains intact across restarts, scaling operations, and platform updates.
Step-by-Step Solution
1. Prepare the Custom Keystore
First, we need to create a new base keystore. We will copy the default system cacerts (which contains standard public CAs) to our persistent storage.
- Connect to your Function App via SSH using the Kudu site (https://<your-app-name>.scm.azurewebsites.net/webssh/host).
- Run the following command to copy the truststore.
(Note: The source path may vary depending on your Java version. You can confirm your exact JVM path by running echo $JAVA_HOME in the console. For example, if it returns /usr/lib/jvm/msft-17-x64, use that path below.)
cp /usr/lib/jvm/msft-17-x64/lib/security/cacerts /home/site/wwwroot/my-truststore.jks
2. Import the Self-Signed Certificate
Upload your root certificate (e.g., self-signed.badssl.com.cer) to the site (you can use drag-and-drop in Kudu or FTP). Then, import it into your new custom keystore.
Run the following command (ensure keytool is in your PATH or navigate to the bin folder):
./keytool -import -alias my-self-signed-cert \
-file /home/self-signed.badssl.com.cer \
-keystore /home/site/wwwroot/my-truststore.jks \
-storepass changeit -noprompt
3. Verify the Import
It is always good practice to verify that the certificate was actually added. Run:
./keytool -list -v \
-keystore /home/site/wwwroot/my-truststore.jks \
-storepass changeit -alias my-self-signed-cert
If successful, you will see the certificate details printed in the console.
4. Configure the Application Setting
Finally, we need to tell the JVM to use our new truststore instead of the default system one.
Go to the Azure Portal > Configuration > Application Settings and add (or update) the JAVA_OPTS setting:
- Name: JAVA_OPTS
- Value: -Djavax.net.ssl.trustStore=/home/site/wwwroot/my-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit
Save the settings. This will restart your Function App, and the JVM will now load your custom truststore at startup.
Important Considerations
File Location & Deployment
In the example above, we placed the keystore in /home/site/wwwroot/.
Warning: Depending on your deployment method (e.g., specific ZipDeploy configurations or "Run From Package"), the contents of /wwwroot might be wiped or overwritten during a new code deployment.
If you are concerned about your deployment process overwriting the .jks file, you can save it in any other folder under /home, for example, /home/my-certs/. Just update the JAVA_OPTS path accordingly.
Maintenance
This is a manual solution. If your self-signed certificate expires:
- You do not need to recreate the whole keystore.
- Simply run the ./keytool -import command again to update the certificate in the existing .jks file.
- Maintaining the validity of the self-signed certificate is your responsibility.
Azure Key Vault Note
You might wonder, "Can I use Azure Key Vault?"
Azure Key Vault is excellent for private keys, but it generally supports importing .pfx or .pem formats for privately signed certificates. Since public .cer certificates are not secrets (they are public, after all), the method above is often the most direct way to handle them for Java trust validation.
Alternative Workarounds
If you prefer not to manage a custom keystore file in the persistent /home directory, here are two alternative approaches. Both of these require modifying your application code.
1. Load the Azure-Managed Certificate via Code
You can upload your .cer public certificate directly to the TLS/SSL settings (Public Keys Certificates) blade in the Azure Portal.
- After uploading, you must add the Application Setting WEBSITE_LOAD_CERTIFICATES with the value * (or the specific certificate thumbprint).
- Azure acts as the OS loader. It places the certificate file at /var/ssl/certs/<thumbprint>.der.
Important Distinction: App Service vs. Function App
There is a difference in how the "Blessed Images" (the default platform images) handle these certificates at startup:
- Azure App Service (Linux): In many scenarios, the platform's startup scripts automatically import these certificates into the JVM keystore.
- Azure Functions (Linux): The Function App runtime does not automatically import these certificates into the JVM keystore during startup. If you SSH into the Function App and run openssl or curl, the connection might succeed because those OS-level tools check the /var/ssl/certs folder. However, your Java application will throw a above handshake error because the JVM only looks at its own cacerts truststore, which is effectively empty of your custom certs.
Since the certificate is present on the disk, you must write Java code to explicitly load this specific file into an SSLContext.
Reference: Use TLS/SSL Certificates in App Code - Azure App Service | Microsoft Learn
2. Build the JKS Locally and Load it via Code
Instead of creating the keystore on the server (the "Best Practice" method), you can create the my-truststore.jks on your local developer machine, include it inside your application (e.g., in src/main/resources), and deploy it as part of your JAR/WAR.
You then write code to load this JKS file from the classpath or file system to initialize your SSL connection.
Reference: Configure Security for Tomcat, JBoss, or Java SE Apps - Azure App Service | Microsoft Learn