ffmpegvideo-processingdevops

FFmpeg 9.0 verifies TLS: fix 'certificate verify failed

·Javid Jamae·10 min read
FFmpeg 9.0 verifies TLS: fix 'certificate verify failed

Your transcode job pulled its input over HTTPS for a year without complaint. You rebuilt the image, the base FFmpeg moved from 8.1.2 to 9.0, and now the job dies before it decodes a single frame. The file is fine, the URL is fine, and the thing that changed is a default you never set.

Quick answer: "ffmpeg certificate verify failed" appears because FFmpeg 9.0 flipped the tls_verify default from 0 to 1, so HTTPS inputs signed by a private or self-signed CA are now rejected during the handshake. Fix it in this order: pass the signing CA with ffmpeg -ca_file /etc/ssl/certs/my-ca.pem -i https://host/file.mp4, or install ca-certificates in the container image so FFmpeg has a trust store at all, and keep -tls_verify 0 for local debugging only. If you'd rather not maintain a trust store in every image, FFmpeg Micro fetches the source URL server-side from one API call, so all your pipeline has to produce is a presigned HTTPS URL with a publicly trusted certificate.

FFmpeg 9.0 didn't break HTTPS, it stopped ignoring it

Conventional wisdom says a TLS error means something is wrong with the server. Most of the time that's true. Here it isn't: the server is exactly as misconfigured as it was last week, and FFmpeg simply stopped looking the other way.

For years FFmpeg's TLS layer shipped with verification off. You could point -i at a self-signed staging box, an internal MinIO instance, or a service behind a corporate MITM proxy, and the handshake completed regardless of who signed the certificate. FFmpeg 9.0 "Lei" (released 3 August 2026, with 9.0.1 on 12 August and 9.0.2 on 19 September) changed the tls_verify default to 1 as part of a release that also hard-removed -vsync, -top, -qphist, and -filter_complex_script, deleted the libnpp filters, and bumped every library major for a full ABI break. The TLS change is the quietest of them and the one that takes down production pipelines, because it fails at input open time with a message that looks like a network problem.

Same command, two versions, on an internal staging host with a self-signed certificate:

$ ffmpeg -version | head -1
ffmpeg version 8.1.2
$ ffmpeg -i https://staging.internal.example/master.mp4 -t 2 -f null -
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'https://staging.internal.example/master.mp4':
frame=   48 fps=0.0 q=-0.0 Lsize=N/A time=00:00:02.00 bitrate=N/A speed=8.7x
$ ffmpeg -version | head -1
ffmpeg version 9.0.2
$ ffmpeg -i https://staging.internal.example/master.mp4 -t 2 -f null -
[tls @ 0x55a4f8c1e340] error:0A000086:SSL routines::certificate verify failed
[in#0 @ 0x55a4f8c1a780] Error opening input: Input/output error
Error opening input file https://staging.internal.example/master.mp4.
Error opening input files: Input/output error

Nothing about the command, the file, or the network changed. Only the default did.

Read the error line, not the Input/output error

The useful information is on the [tls @ ...] line, and the Input/output error underneath it is noise that sends people debugging DNS and firewalls. What the TLS line says depends on which library your FFmpeg was built against, so check that first with ffmpeg -version | grep -o 'enable-[a-z]*tls\|enable-openssl\|enable-gnutls\|enable-mbedtls'.

What you seeWhat it actually means
`error:0A000086:SSL routines::certificate verify failed` (OpenSSL 3.x)The chain didn't validate: unknown CA, missing intermediate, or expired leaf
`The certificate is NOT trusted. The certificate issuer is unknown.` (GnuTLS)Same unknown-CA case, phrased by GnuTLS
`Cannot verify SSL certificate` / `unable to get local issuer certificate`No trust store present in the image at all
`Hostname mismatch` or `The certificate's owner does not match hostname`Cert is trusted but issued for a different name than the URL host
`certificate is not yet valid`Container clock skew, not a certificate problem

A Plex forum thread running the same error string shows how this reads to people who don't know the default changed: the report is "my library scanner stopped fetching metadata," and the answer buried in the replies is that the container has no CA bundle. Error strings are where this cluster lives. FFmpeg's own trac.ffmpeg.org/wiki/Errors page is a stub, so the real diagnosis is always somebody's issue tracker.

The three fixes, in order of correctness

Pick the first one that applies to your situation, because the last one trades a real security property for a fast green build. All three assume FFmpeg 9.0 or newer; on 8.1.2 and earlier none of them are necessary for a private CA.

Pass the CA that signed the certificate

Use -ca_file when the cert is signed by a CA you control, such as an internal PKI root or the self-signed cert on a staging endpoint. It's an option on the TLS protocol, so it goes before -i:

ffmpeg -ca_file /etc/ssl/certs/internal-root.pem \
  -i https://staging.internal.example/master.mp4 \
  -c:v libx264 -crf 23 -c:a copy out.mp4

This is the correct fix because verification stays on. FFmpeg validates the chain, just against a root you told it to trust. If your build uses OpenSSL, SSL_CERT_FILE=/etc/ssl/certs/internal-root.pem does the same thing through the environment, which is handy when the command is generated by a library like fluent-ffmpeg and you can't easily inject flags.

Install the CA bundle in the image

Do this when the certificate is already publicly trusted and FFmpeg still rejects it. That combination almost always means the image has no /etc/ssl/certs to check against. Slim and Alpine bases ship without one, and a static FFmpeg binary dropped into a scratch image has nothing at all:

# Alpine
RUN apk add --no-cache ca-certificates && update-ca-certificates

# Debian/Ubuntu slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
 && rm -rf /var/lib/apt/lists/*

Distroless and other minimal bases can't run a package manager, so copy the bundle from a builder stage with COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/. That's the same class of problem as n8n's distroless image breaking FFmpeg Dockerfiles: the binary is present, its runtime environment isn't.

Turn verification off, in a scratch shell only

-tls_verify 0 restores the pre-9.0 behavior and belongs in an interactive debugging session, never in a deployed pipeline:

ffmpeg -tls_verify 0 -i https://staging.internal.example/master.mp4 -t 2 -f null -

If that command succeeds, you've confirmed the certificate chain is the only thing wrong, and you can go fix it properly with one of the two options above. Shipping it means any host that can answer for that DNS name can feed arbitrary bytes into your decoder.

Pitfalls that produce the same message for different reasons

Three of these bite hardest in containers, where the failure looks identical to an untrusted-CA failure but has nothing to do with your CA.

  • Missing intermediates. The server sends only its leaf cert, so browsers succeed (they cache intermediates) and FFmpeg fails. Confirm with openssl s_client -connect host:443 -showcerts and count the certs returned. Fix it on the server, not in FFmpeg.
  • Clock skew. A container whose clock drifted past the cert's validity window reports verification failure. Check date inside the container before touching any flags.
  • Corporate TLS inspection. Zscaler, Netskope, and similar proxies re-sign traffic with their own root. Your laptop trusts it, your CI runner doesn't. Add the proxy root with -ca_file.
  • Hostname mismatch on presigned URLs. Pointing a path-style S3 URL at a custom endpoint whose cert covers only *.s3.amazonaws.com fails verification, not authorization. -verifyhost 0 isolates this, then use the correct virtual-hosted URL.
  • Flag placement. -ca_file and -tls_verify are input options. Put them after -i and FFmpeg either ignores them or errors on an unrecognized option.

Hosted video APIs never see your container's trust store

The pipeline-level version of this problem is worth stating plainly, because it changes what you should build. When a hosted API fetches your source URL server-side, it runs in its own environment with its own trust store, and there is no flag you can send it that makes your internal CA trustworthy. Your self-signed staging endpoint is unreachable to it in the way that matters, and private hostnames don't resolve from outside your VPC anyway.

The workable pattern is the one from giving FFmpeg a presigned URL instead of downloading from S3: generate a time-limited HTTPS URL on a publicly trusted certificate (S3, R2, GCS all do this), hand that to the processor, and let it stream the bytes it needs. That's exactly how FFmpeg Micro works. You POST a job with a source URL, it fetches and processes server-side, and you get the output back by poll or webhook. No encoder to host, no CA bundle per image, and no FFmpeg major version upgrade quietly changing a default under your workflow. The full request shape is in the docs.

Compare the two maintenance paths: with self-hosted FFmpeg you own the trust store in every image, for every base, across every version bump. With a hosted call you own one thing, which is producing a URL that a normal HTTPS client can validate.

FAQ

Is `-tls_verify 0` safe to leave in a production FFmpeg command?

-tls_verify 0 is not safe in production because it disables certificate validation entirely, which means FFmpeg will accept a certificate from any host that answers for the URL's DNS name. Use it to confirm the diagnosis in a shell, then switch to -ca_file with the real signing CA.

Which FFmpeg version changed the `tls_verify` default?

FFmpeg 9.0, released 3 August 2026, changed the tls_verify default from 0 to 1. FFmpeg 8.1.2 and every earlier release left verification off, which is why the same HTTPS input command succeeds on 8.1.2 and fails after the upgrade.

Where does `-ca_file` go in an FFmpeg command?

-ca_file is a TLS protocol option and must appear before the -i it applies to, as in ffmpeg -ca_file /etc/ssl/certs/root.pem -i https://host/file.mp4 out.mp4. Placed after the input, FFmpeg treats it as an output option and the verification failure continues.

Why does curl fetch the URL but FFmpeg can't?

curl and FFmpeg use different trust configuration, so curl succeeding proves only that curl found a usable CA bundle. curl on the same host may read /etc/ssl/certs while a statically linked FFmpeg build looks at a compiled-in path that doesn't exist in the image, or the two may be built against different TLS libraries entirely.

Can I set the CA bundle with an environment variable instead of a flag?

OpenSSL-based FFmpeg builds respect SSL_CERT_FILE and SSL_CERT_DIR, which is the practical option when a wrapper library such as fluent-ffmpeg or an n8n node builds the command for you. GnuTLS builds ignore those variables, so check ffmpeg -version for --enable-openssl before relying on this.

If your video steps keep inheriting someone else's defaults, move the encode off your containers: sign up free, send a presigned URL, and let the job run on a toolkit that's already patched and already trusted.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free