Android SSL Certificate Pinning A Practical Guide

October 19, 2025

Android SSL Certificate Pinning A Practical Guide

At its core, Android SSL certificate pinning is a security tactic where an app is hardcoded to trust only a specific, predefined server certificate. This bypasses the normal trust chain and creates a direct, exclusive relationship between your app and your server.

Think of it as giving your app a secret handshake for your server; no other handshake, no matter how legitimate it seems, will be accepted.

The Rise and Fall of Certificate Pinning#

Certificate pinning used to be the gold standard for developers building apps where security was paramount, especially in the finance and healthcare sectors. The goal was simple but powerful: make sure the app talks only to the one true server, shutting down a major avenue for man-in-the-middle attacks.

By embedding the server’s public key or the entire certificate right into the app, developers could sidestep the device’s standard collection of trusted certificate authorities.

This created a rigid but incredibly secure communication channel. The benefits were obvious:

  • Enhanced Security: It offered rock-solid protection against man-in-the-middle (MITM) attacks, where an attacker might intercept traffic using a rogue certificate from a compromised Certificate Authority (CA).
  • Explicit Trust: Instead of blindly trusting hundreds of CAs stored on a device—where any one could be a weak link—the app trusts only the one you’ve personally vouched for.

But this rigidity eventually became its greatest weakness. Before you go too far down the pinning rabbit hole, it helps to have a solid grasp of secure communication basics, including the different types of encryption. The operational headaches and risks tied to pinning have since led the broader security community, including Google, to recommend against it for most use cases.

The infographic below really puts the old-school approach in perspective against modern, more flexible security practices.

Infographic comparing traditional SSL pinning with modern security practices, showing risks and flexibility.

As you can see, the high risk of completely breaking your app has pushed the industry toward configurations that are both secure and manageable.

Why Pinning Fell Out of Favor#

The main problem is just how brittle pinning is. A simple, routine server certificate renewal—something that happens all the time—could instantly render your app useless for every single user. Why? Because the app, with its hardcoded pin, would no longer trust the new, perfectly valid certificate.

The only way to fix it is to push out a new version of the app and hope your users update it. That’s a recipe for significant downtime, angry users, and a support nightmare. For a more detailed look at the mechanics, you might be interested in our comprehensive overview of what SSL pinning is.

This practice has become a relic of the past, with some studies showing its usage has plummeted to as low as 0.9% of apps. Both Google and Apple now actively discourage pinning because it’s just too fragile and carries a high risk of causing widespread connectivity failures for your users.

If you've decided that your app absolutely needs SSL pinning, the modern, Google-endorsed way to do it is with a Network Security Config file. This approach, introduced back in Android 7.0 (Nougat), is a massive improvement over the old-school, programmatic techniques.

It's a declarative method, which means you define your security rules in a clean XML file, completely separate from your Kotlin or Java code.

This separation is a game-changer. Imagine you need to update a pinned certificate. Instead of hunting through your networking logic, you just tweak an XML file. This makes maintenance far less painful and much less prone to error.

An illustration of a network security configuration file with XML tags.

Creating Your Configuration File#

First things first, you need to create the XML file itself. Inside your Android Studio project, head over to res/xml and create a new file named network_security_config.xml. If that xml directory doesn't exist yet, just go ahead and create it.

Next, you have to tell your app to actually use this new config. Crack open your AndroidManifest.xml and find the <application> tag. You'll add a single attribute to it, pointing to the file you just made.

<application
...
android:networkSecurityConfig="@xml/network_security_config">
...

That one line is all it takes to link your app's entire networking behavior to the rules you're about to define.

Defining Your Pin Set#

Now for the important part. Inside your network_security_config.xml file, you'll lay out the rules. The heart of this setup is the <pin-set>, which is where you list the public key hashes of the certificates you've chosen to trust.

Here’s a practical example for a domain like api.your-awesome-app.com:

api.your-awesome-app.com your_primary_pin_hash_here== your_backup_pin_hash_here==

Let's break down what's going on here:

  • <domain-config>: This is just a container for rules that apply to specific domains.
  • <domain includeSubdomains="true">: This specifies which domain the rule applies to. I almost always set includeSubdomains to true for consistency across an API.
  • <pin-set expiration="...">: This is the real meat of the configuration. It defines your set of trusted public key hashes and, crucially, an expiration date. Always, always set an expiration date. It's a forcing function that makes you re-evaluate your pins and prevents you from accidentally locking users out of your app forever.
  • <pin digest="SHA-256">: Each one of these tags holds a Base64 encoded SHA-256 hash of a certificate's Subject Public Key Info (SPKI).

Critical Takeaway: Never, ever pin to a single certificate. It's a recipe for disaster. You must include at least one backup pin. This backup could be the public key from a future certificate you've already generated, or it could be from one of the intermediate CAs in your certificate chain.

This XML-based approach gives you a clear, manageable way to handle pinning. The declarative nature of the file makes it easy for anyone on the team—even someone who doesn't live and breathe networking code—to understand the app's security rules at a glance.

For developers who are bridging web technologies and native shells, getting comfortable with native configs like this is key. It's a lot like learning how to build a Capacitor app; the real skill is in understanding how the web and native layers talk to each other. Ultimately, this method is far safer and more maintainable than older, programmatic approaches.

Handling Pinning Programmatically with OkHttp#

While the declarative network_security_config.xml is the go-to method for most Android apps today, sometimes you need to get your hands dirty with code. Maybe you're stuck maintaining a legacy codebase that predates the Network Security Config, or you have a slick setup where pinning rules are fetched dynamically from a server at runtime.

When you find yourself in one of those situations, you'll need to handle pinning programmatically.

A code snippet showing programmatic SSL pinning with OkHttp on a dark background.

The best tool for the job in modern Android development is, without a doubt, the OkHttp library. It gives you a clean, powerful API called CertificatePinner to build your pinning rules directly into your OkHttpClient instance. This keeps your security logic tightly coupled with your networking layer, right where it belongs.

Implementing Pinning with CertificatePinner#

Let's jump into a practical Kotlin example. The idea is simple: you create a CertificatePinner, tell it which domains to watch and what public key hashes to expect, and then attach it to your OkHttpClient.

Here’s what that looks like in action:

val hostname = "api.your-awesome-app.com"

val certificatePinner = CertificatePinner.Builder()
.add(hostname, "sha256/your_primary_pin_hash_here==")
.add(hostname, "sha256/your_backup_pin_hash_here==")
.build()

val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()

What we're doing here is telling our HTTP client that for any request to api.your-awesome-app.com, the server's certificate chain must contain a public key matching one of the SHA-256 hashes we've provided. If there's no match, OkHttp instantly throws an SSLPeerUnverifiedException, killing the connection on the spot. It's exactly the kind of hard failure you want from a security feature.

Pro Tip: Don't worry about how to get these hashes. The process is the same as it is for the Network Security Config. You can use tools like openssl or an online generator to extract the Subject Public Key Info (SPKI) hash from your server's certificate.

The Critical Need for Backup Pins#

I can't stress this enough: never pin to a single certificate. Pinning to just one key is a ticking time bomb. As soon as that certificate expires and you rotate it, your app will break for every single user. They'll be completely cut off from your server until they update to a new version of the app with the new pin. It's a self-inflicted outage.

To avoid this disaster, you absolutely must include at least one backup pin. A solid backup strategy could involve:

  • Pinning a future certificate's key: Generate your next certificate ahead of time and include its public key hash as a backup pin in the current app release.
  • Pinning an intermediate CA: Pin the public key of an intermediate Certificate Authority in your server's certificate chain. This gives you more flexibility, as any certificate issued by that CA will be trusted.
  • Pinning your root CA: This is the most flexible option but also the least secure. It protects you from completely rogue CAs but not from a compromise of your specific CA.

While programmatic pinning is a powerful tool, it does add a layer of complexity to your code. This is especially true when you're also trying to manage secrets like API keys, which often live in the same networking layer. For a deeper look into protecting those, check out our guide on securing API keys in mobile apps.

What About HttpURLConnection?#

For a bit of historical context, it's worth mentioning the old-school HttpURLConnection. Before libraries like OkHttp became the standard, developers had to implement pinning manually by creating a custom TrustManager and overriding its checkServerTrusted method.

Frankly, it's a nightmare. The approach is incredibly verbose, notoriously error-prone, and a real pain to maintain.

You might still run into this pattern in very old codebases, but for any new development—or even when refactoring—you should move away from it. OkHttp's CertificatePinner is cleaner, safer, and just plain easier to implement correctly. It’s a great example of how the wider adoption of HTTPS, now used by about 88% of websites, has pushed the industry to create better, more accessible security tools for developers. You can explore more data on this trend in this report on SSL key statistics and trends.

Avoiding Common SSL Pinning Mistakes#

Putting SSL pinning in place can feel like adding a bank vault door to your app's network security. But one wrong move can leave your users locked out. I’ve seen it happen, and it's a brutal, self-inflicted outage. Let's walk through the most common traps so you can sidestep them entirely.

An image showing a red warning sign with a broken padlock icon, symbolizing security mistakes.

The single most tempting—and dangerous—mistake is pinning just the leaf certificate. That’s the one issued directly to your server's domain. It feels direct and simple, but it's incredibly fragile.

Leaf certificates have short lifespans. They expire, often annually. When that certificate gets renewed, your app, which only trusts the old certificate's public key, will immediately break for every single user. The only fix is an emergency app update, and then you just have to hope everyone installs it before they give up and uninstall. It turns a security feature into a production catastrophe.

Build a Resilient Pinning Strategy#

A good pinning strategy is built for change. You have to assume your certificates will change, because they will. The most critical rule is to never rely on a single pin. Ever. Your implementation must have backups ready to handle certificate rotations—both planned and unplanned—without taking your app offline.

Here’s what you need to do:

  • Always Include Backup Pins: Your pin set needs at least two hashes. Pin the public key of your current certificate, and add a backup. This backup could be from a second certificate you've already generated and have waiting in the wings.
  • Consider Pinning an Intermediate CA: A more flexible approach is to pin the public key of an intermediate Certificate Authority in your server's chain. This lets you rotate your leaf certificate as often as you need, as long as the new one is signed by that same trusted intermediate.
  • Have a Solid Rotation Plan: Don't just set it and forget it. You need a documented, tested process for rotating your certificates and updating the pins in your app well before the old ones expire.

Remember, the point of pinning is to lock down your app's security, not create a maintenance nightmare. A smart strategy with multiple backup pins gives you that security without the brittleness that plagues so many implementations.

Control Your Dependencies#

Another huge risk is pinning certificates for third-party APIs you don't control. I know it’s tempting to secure connections to services like analytics platforms or payment gateways, but you have no say in when they rotate their certificates.

If a third-party service you’ve pinned swaps out its certificate without warning, a core part of your app could break instantly, and there's nothing you can do about it. Stick to pinning only for endpoints that are 100% within your organization's control.

Building a secure mobile app means understanding your entire ecosystem. For a broader look, it’s worth exploring some fundamental mobile app security best practices that cover more than just network connections.

How to Test Your Pinning Implementation#

Alright, you’ve written the code for your Android SSL certificate pinning. That's only half the job. You have to be absolutely sure it holds up under pressure, because simply seeing your app connect successfully in a normal environment tells you nothing about its security.

The real proof comes when you try to break it. You need to simulate a man-in-the-middle (MITM) attack, and for that, proxy tools are your best friends. Tools like Charles Proxy or mitmproxy are essential for this part of the process. They sit between your app and your server, pretending to be a malicious actor trying to slip you an untrusted certificate.

Simulating an Attack with a Proxy#

First things first, you need to get your test device or emulator to route all its network traffic through the proxy tool running on your computer. This means digging into the Wi-Fi settings on your Android device and pointing the proxy to your computer's local IP address and the port your tool is listening on.

Next, you'll install the proxy's root certificate on that same test device. This is a critical step because it tells the Android OS to trust any certificate the proxy generates, allowing it to intercept and decrypt HTTPS traffic from other apps. Your pinned app, however, should know better.

A successful test is a failed connection. When you launch your app and it tries to make a network request, it should flat-out refuse to connect through the proxy. Instead of seeing traffic flowing through Charles, you should get an immediate connection error.

This failure is your victory lap. Check your app's logs in Logcat, and you should find a smoking gun—usually an SSLHandshakeException or SSLPeerUnverifiedException.

This error confirms your pinning implementation did its job. It correctly identified the proxy's phony certificate as untrusted and shut down the connection, which is exactly what it should do during a real attack. This kind of hands-on validation is a core part of any serious security check, something we cover in more detail in our complete mobile app testing checklist.

A Few Common Questions About Android SSL Pinning#

As we wrap this up, you probably have a few questions rattling around. The world of Android SSL certificate pinning is full of nuance, and making the right call for your app’s security is a big deal. Let's walk through some of the most common questions I hear from developers to clear up any final doubts.

These aren’t just theoretical what-ifs; they’re the real-world headaches of mobile security. The landscape is littered with apps that get this wrong. A recent study of top-grossing Android e-commerce apps found that a staggering 92% used unsecured HTTP connections for some functionality. That stat alone shows just how widespread security gaps are, and it’s why understanding techniques like pinning is so critical. You can get all the details in the full research on mobile app security practices.

Why Is SSL Certificate Pinning So Risky?#

The biggest risk is that it's just so brittle. If your server's certificate changes for any reason—even a standard, scheduled renewal—your app will instantly lose connectivity for every single user.

This triggers a massive, app-breaking outage that you can only fix by shipping a new update and praying your users install it quickly. The high potential for self-inflicted downtime is the main reason why the industry has largely moved away from it.

What Are Better Alternatives to SSL Pinning?#

Instead of relying on a single, fragile defense, the modern approach is to focus on robust security layers that don't carry the same operational risks. This creates a much stronger defense without the single point of failure that pinning introduces.

Your best strategy includes a few key pieces:

  • Enforce HTTPS-Only Traffic: This is your first and most important line of defense. Use Android's Network Security Configuration to block all cleartext traffic. No exceptions.
  • Implement HSTS on Your Server: HTTP Strict Transport Security (HSTS) is a simple header that tells browsers and apps to only communicate with your server over HTTPS.
  • Monitor Certificate Transparency (CT) Logs: Actively watching CT logs will alert you if a certificate is ever mistakenly or maliciously issued for your domain, giving you a heads-up on potential trouble.

This combination gives you excellent protection against common attacks without the operational nightmare of pinning.

Are There Any Situations Where Pinning Is Still a Good Idea?#

While it’s become quite rare, pinning might still be on the table for applications with extreme security requirements. Think high-value financial apps or sensitive enterprise tools.

Even then, it's only a viable option if you have complete, iron-clad control over the server and a rigorous, well-tested process for certificate rotation and mandatory app updates.

For over 99% of apps, the risk of a self-inflicted outage far outweighs the niche security benefit that pinning provides. Modern alternatives offer a much better balance of security and reliability for the vast majority of use cases.

Ultimately, the decision comes down to a careful risk assessment. Pinning adds a powerful but incredibly fragile layer of security. Unless you are absolutely certain you can manage its lifecycle perfectly, sticking to modern best practices is the safer and more sustainable choice for your Android app.


Building secure, production-ready mobile apps from a web-based codebase can be complex. NextNative provides a complete toolkit to bridge that gap, letting you focus on features instead of wrestling with native configurations. Discover how NextNative can help you build and launch your mobile app faster.

Explore more