Quick answer
Update the pinned certificate hash in your app's network_security_config.xml file with the new server certificate SHA-256 hash.
Why this happens
Certificate pinning is a security feature. It tells your app: "Only trust this one specific server certificate, not any other." When the server updates its certificate (maybe it expired, or they switched providers), your app still looks for the old one. This mismatch triggers the SSLHandshakeException: Chain validation failed error.
I've seen this a lot on Android apps that talk to APIs. The server team swaps certificates, and suddenly users can't log in or see data. On the device side, you'll see the error in logcat: javax.net.ssl.SSLHandshakeException: Chain validation failed.
Step-by-step fix
We'll update the certificate hash in the Android app's network configuration. You'll need the new certificate from the server team.
- Get the new server certificate. Ask your server admin for the PEM file. Or grab it yourself using OpenSSL:
After running this, you should see a base64 hash likeopenssl s_client -connect yourserver.com:443 -servername yourserver.com 2>/dev/null | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -binary | base6447DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=. Write that down. - Open your app's
network_security_config.xml. This file lives inres/xml/folder of your Android project. If it's not there, create it. - Replace the old hash with the new one. Find the
<pin>tags inside the config. It should look like:
Change the old hash to the new one. Save the file.<domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">yourserver.com</domain> <pin-set> <pin digest="SHA-256">47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=</pin> </pin-set> </domain-config> - Rebuild the app. Clean and rebuild your project. On Android Studio, go to Build > Clean Project, then Build > Rebuild Project. After this, the app should use the new hash.
- Test the app. Run it on a device or emulator. The error should disappear. If it doesn't, check you copied the hash right—it's easy to miss a character.
If the main fix doesn't work
Sometimes the server admin didn't update the certificate chain properly. Here are three alternatives:
- Temporarily disable pinning for testing. In
network_security_config.xml, setcleartextTrafficPermitted="true"and remove the<pin-set>block. This lets any certificate through. Don't leave it this way for production—it's a security risk. - Check the server's intermediate certificates. Run
openssl s_client -connect yourserver.com:443 -showcerts. Look for missing intermediate CAs. If the server doesn't send them, you might need to include them in the pin set. - Add multiple pins. Pin the public key of the root CA and the intermediate CA. Then if one certificate changes, the other still works. Do this:
<pin-set> <pin digest="SHA-256">47DEQ...first_hash=</pin> <pin digest="SHA-256">9aBTd...second_hash=</pin> </pin-set>
How to prevent this
Set up a process with your server team. They should tell you at least 30 days before a certificate expires. Then you can test the new certificate in a staging environment before it goes live. Also, use certificate transparency logs (like crt.sh) to monitor when your server certificates change. A simple script that checks your server's certificate SHA-256 daily and emails you if it's different will save you headaches.