[META] Experimental network features #5129

Open
opened 2026-02-05 11:10:12 +03:00 by OVERLORD · 126 comments
Owner

Originally created by @mmomjian on GitHub (Jan 10, 2025).

Experimental network features

Features

There are some network features that have been added to the Immich mobile app through the years that interact with various libraries and background tasks in unexpected ways. These features include:

  • HTTP Basic Auth for server URL
  • Self-signed SSL certs
  • Manually imported root CA / SSL certificate
  • Mutual TLS
  • Custom HTTP proxy headers

Limitations

Typically, these network features work fine in the normal library view, but cause issues with:

  • video playback
  • foreground asset upload
  • background asset upload
  • interrupted uploads
  • asset download
  • App crashing

Solutions

Thanks to the recent implementation of auto-switching server URLs in #14437, many of these features are no longer needed for a smooth Immich experience. There are other options, including the use of a VPN or a real SSL cert.

Handling known issues

We have updated the FAQ (link will work once #15228 is merged). This issue can be used to track any known issues with these experimental network features. These issues will not be a priority for the dev team, but can be improved by the community if possible.

Please do not use this discussion for comments like "same" or +1". This is designed only to keep track of current known limitations to these experimental features, or discuss potential solutions and future patches.

Originally created by @mmomjian on GitHub (Jan 10, 2025). # Experimental network features ## Features There are some network features that have been added to the Immich mobile app through the years that interact with various libraries and background tasks in unexpected ways. These features include: - HTTP Basic Auth for server URL - Self-signed SSL certs - Manually imported root CA / SSL certificate - Mutual TLS - Custom HTTP proxy headers ## Limitations Typically, these network features work fine in the normal library view, but cause issues with: - video playback - foreground asset upload - background asset upload - interrupted uploads - asset download - App crashing ## Solutions Thanks to the recent implementation of auto-switching server URLs in #14437, many of these features are no longer needed for a smooth Immich experience. There are other options, including the use of a VPN or a real SSL cert. ## Handling known issues We have updated the [FAQ](https://immich.app/docs/FAQ#why-are-features-in-the-mobile-app-not-working-with-a-self-signed-certificate-basic-auth-custom-headers-or-mutual-tls) (link will work once #15228 is merged). This issue can be used to track any known issues with these experimental network features. These issues will not be a priority for the dev team, but can be improved by the community if possible. ### Please do not use this discussion for comments like "same" or +1". This is designed only to keep track of current known limitations to these experimental features, or discuss potential solutions and future patches.
Author
Owner

@rovo89 commented on GitHub (Jan 10, 2025):

The final sentences sound quite intimidating, so I'm wondering: Is it allowed to discuss analysis of root causes and potential solutions here? Or where would that go?

@rovo89 commented on GitHub (Jan 10, 2025): The final sentences sound quite intimidating, so I'm wondering: Is it allowed to discuss analysis of root causes and potential solutions here? Or where would that go?
Author
Owner

@mmomjian commented on GitHub (Jan 10, 2025):

The final sentences sound quite intimidating, so I'm wondering: Is it allowed to discuss analysis of root causes and potential solutions here? Or where would that go?

If the discussion is relevant to potential future PRs or improvements, or the actual underlying issue, that's fine. Often these topics turn into people repetitively saying "I want this feature too", that is what we are trying to avoid

@mmomjian commented on GitHub (Jan 10, 2025): > The final sentences sound quite intimidating, so I'm wondering: Is it allowed to discuss analysis of root causes and potential solutions here? Or where would that go? If the discussion is relevant to potential future PRs or improvements, or the actual underlying issue, that's fine. Often these topics turn into people repetitively saying "I want this feature too", that is what we are trying to avoid
Author
Owner

@ckuyehar commented on GitHub (Jan 10, 2025):

I propose that we use the discussions area to discuss a specific feature. If you start a discussion, mention it in the discussion and ask @mmomjian to update the top comment so that it links to the discussion.

The moment, two or more features are discussed in this single thread, it's going to get chaotic.

@ckuyehar commented on GitHub (Jan 10, 2025): I propose that we use the [discussions area](https://github.com/immich-app/immich/discussions/categories/general) to discuss a specific feature. If you start a discussion, mention it in the discussion and ask @mmomjian to update the top comment so that it links to the discussion. The moment, two or more features are discussed in this single thread, it's going to get chaotic.
Author
Owner

@bo0tzz commented on GitHub (Jan 10, 2025):

It is already know that these features do not work well, so we should avoid rehashing "it's broken" over and over again.
I would propose instead to keep this thread focussed on very specific discussion of why things are not working (eg referencing specific code paths and such) that might result in a PR. That way things should not get chaotic, and there should be no need for more separate issues or discussions (which is chaotic in its own way).

@bo0tzz commented on GitHub (Jan 10, 2025): It is already know that these features do not work well, so we should avoid rehashing "it's broken" over and over again. I would propose instead to keep this thread focussed on very specific discussion of _why_ things are not working (eg referencing specific code paths and such) that might result in a PR. That way things should not get chaotic, and there should be no need for more separate issues or discussions (which is chaotic in its own way).
Author
Owner

@rovo89 commented on GitHub (Jan 10, 2025):

As per https://github.com/immich-app/immich/issues/14845#issuecomment-2584007919, "app crashes" should be added to the list of limitations. Happens on Android whenever I navigate to a video, e.g. by clicking on it in the timeline or when swiping through it. I don't even need to click the "play" button to provoke the crash.

@rovo89 commented on GitHub (Jan 10, 2025): As per https://github.com/immich-app/immich/issues/14845#issuecomment-2584007919, "app crashes" should be added to the list of limitations. Happens on Android whenever I navigate to a video, e.g. by clicking on it in the timeline or when swiping through it. I don't even need to click the "play" button to provoke the crash.
Author
Owner

@rovo89 commented on GitHub (Jan 10, 2025):

As far as analysis is concerned, I'm copying my findings from https://github.com/immich-app/immich/issues/5553#issuecomment-2582523158 which is closed meanwhile. My setup uses mTLS (client certificates) and a Let's Encrypt server certificate, I'm using Android.


A major problem seems to be the many levels of abstraction and different packages used. Flutter (using Dart), Jetpack, Android framework... that make it hard to follow the call chain and pass the required information down to the places where connections are actually used.

For most parts of the app, Immich uses HttpSSLCertOverride to set the client certificate and allow self-signed certificates (for the Immich server host only), which is set globally here. But I think that only applies to places where Dart's standard HttpClient class is used. Under the hood, I think it makes adjustments to OpenSSL's SSLCertContext which is wrapped by Dart's SecurityContext. My understanding is that the native HTTP client isn't involved at all here, but OpenSSL is used directly.

The video player seems to use the Android native HTTP client which is created here. DefaultHttpDataSource comes from here and uses HttpURLConnection. IIUC, Android uses a customized OpenJDK with their own handlers, in this case com.android.okhttp.HttpsHandler. This seems to be the place where they set SSL options, which uses HttpsURLConnection.getDefaultSSLSocketFactory(). I haven't tracked it down completely yet, but I think SSLContext.init() is where a client certificate would be specified.

I have just written this down to understand the current flow and maybe get corrections from someone. Next step would be finding a way to pass the client certificate to the appropriate place without breaking abstraction... Maybe it would be sufficient to set call HttpsURLConnection.setDefaultSSLSocketFactory() in Immich code?

For completeness, the download library has some code to accept untrusted certificates which calls exactly that method with an accept-all trust manager, indicating that it could profit from this approach as well.

@rovo89 commented on GitHub (Jan 10, 2025): As far as analysis is concerned, I'm copying my findings from https://github.com/immich-app/immich/issues/5553#issuecomment-2582523158 which is closed meanwhile. My setup uses mTLS (client certificates) and a Let's Encrypt server certificate, I'm using Android. --- A major problem seems to be the many levels of abstraction and different packages used. Flutter (using Dart), Jetpack, Android framework... that make it hard to follow the call chain and pass the required information down to the places where connections are actually used. For most parts of the app, Immich uses [HttpSSLCertOverride](https://github.com/immich-app/immich/blob/main/mobile/lib/utils/http_ssl_cert_override.dart) to set the client certificate and allow self-signed certificates (for the Immich server host only), which is set globally [here](https://github.com/immich-app/immich/blob/7d50d3032bff9c32c1d001ce40ac184444009d5b/mobile/lib/main.dart#L51). But I think that only applies to places where Dart's standard [HttpClient](https://api.flutter.dev/flutter/dart-io/HttpClient-class.html) class is used. Under the hood, I think it makes [adjustments](https://github.com/dart-lang/sdk/blob/4bcd8c2b8ce56756dbe7dd762af7f9322a777402/runtime/bin/security_context.cc#L797) to OpenSSL's SSLCertContext which is wrapped by Dart's SecurityContext. My understanding is that the native HTTP client isn't involved at all here, but OpenSSL is used directly. The video player seems to use the Android native HTTP client which is created [here](https://github.com/immich-app/native_video_player/blob/05ad661b338dad1a5cdac099630e6add351a708b/android/src/main/kotlin/me/albemala/native_video_player/NativeVideoPlayerViewController.kt#L74). DefaultHttpDataSource comes from [here](https://github.com/androidx/media/blob/release/libraries/datasource/src/main/java/androidx/media3/datasource/DefaultHttpDataSource.java) and uses [HttpURLConnection](https://github.com/androidx/media/blob/76088cd6af7f263aba238b7a48d64bd4f060cb8b/libraries/datasource/src/main/java/androidx/media3/datasource/DefaultHttpDataSource.java#L610). IIUC, Android uses a customized OpenJDK with their own [handlers](https://android.googlesource.com/platform/libcore/+/5738890c2aed47e7d6bc4516f9e07931e2a21bdb/ojluni/src/main/java/java/net/URL.java#1286), in this case [com.android.okhttp.HttpsHandler](https://android.googlesource.com/platform/external/okhttp/+/refs/heads/main/android/src/main/java/com/squareup/okhttp/HttpsHandler.java). [This](https://android.googlesource.com/platform/external/okhttp/+/refs/heads/main/android/src/main/java/com/squareup/okhttp/HttpsHandler.java#92) seems to be the place where they set SSL options, which uses HttpsURLConnection.getDefaultSSLSocketFactory(). I haven't tracked it down completely yet, but I think SSLContext.init() is where a client certificate would be specified. I have just written this down to understand the current flow and maybe get corrections from someone. Next step would be finding a way to pass the client certificate to the appropriate place without breaking abstraction... Maybe it would be sufficient to set call HttpsURLConnection.setDefaultSSLSocketFactory() in Immich code? For completeness, the download library has [some code](https://github.com/781flyingdutchman/background_downloader/blob/main/android/src/main/kotlin/com/bbflight/background_downloader/Helpers.kt) to accept untrusted certificates which calls exactly that method with an accept-all trust manager, indicating that it could profit from this approach as well.
Author
Owner

@ckuyehar commented on GitHub (Jan 11, 2025):

Custom HTTP proxy headers

  • I think this issue refers to using the "custom proxy headers" in the Immich mobile app.
  • I use this feature and I have no issue with this.

Can someone refer an issue related to this one?

@ckuyehar commented on GitHub (Jan 11, 2025): ### Custom HTTP proxy headers - I think this issue refers to using the "custom proxy headers" in the Immich mobile app. - I use this feature and I have no issue with this. Can someone refer an issue related to this one?
Author
Owner

@mmomjian commented on GitHub (Jan 11, 2025):

Custom HTTP proxy headers

  • I think this issue refers to using the "custom proxy headers" in the Immich mobile app.

  • I use this feature and I have no issue with this.

Can someone refer an issue related to this one?

It's possible there's no known issues. However we still consider it an experimental feature. Glad it's working well!

@mmomjian commented on GitHub (Jan 11, 2025): > ### Custom HTTP proxy headers > > - I think this issue refers to using the "custom proxy headers" in the Immich mobile app. > > - I use this feature and I have no issue with this. > > > > Can someone refer an issue related to this one? > > It's possible there's no known issues. However we still consider it an experimental feature. Glad it's working well!
Author
Owner

@rovo89 commented on GitHub (Jan 11, 2025):

As for the crash in the video player, here is the trace with a debug build:

E/ExoPlayerImplInternal(10356): Playback error
E/ExoPlayerImplInternal(10356):   androidx.media3.exoplayer.ExoPlaybackException: Source error
E/ExoPlayerImplInternal(10356):       at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:737)
E/ExoPlayerImplInternal(10356):       at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:709)
E/ExoPlayerImplInternal(10356):       at android.os.Handler.dispatchMessage(Handler.java:105)
E/ExoPlayerImplInternal(10356):       at android.os.Looper.loopOnce(Looper.java:232)
E/ExoPlayerImplInternal(10356):       at android.os.Looper.loop(Looper.java:317)
E/ExoPlayerImplInternal(10356):       at android.os.HandlerThread.run(HandlerThread.java:85)
E/ExoPlayerImplInternal(10356):   Caused by: androidx.media3.datasource.HttpDataSource$InvalidResponseCodeException: Response code: 400
E/ExoPlayerImplInternal(10356):       at androidx.media3.datasource.DefaultHttpDataSource.open(DefaultHttpDataSource.java:401)
E/ExoPlayerImplInternal(10356):       at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:87)
E/ExoPlayerImplInternal(10356):       at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1085)
E/ExoPlayerImplInternal(10356):       at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:450)
E/ExoPlayerImplInternal(10356):       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
E/ExoPlayerImplInternal(10356):       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
E/ExoPlayerImplInternal(10356):       at java.lang.Thread.run(Thread.java:1012)
D/AndroidRuntime(10356): Shutting down VM
E/AndroidRuntime(10356): FATAL EXCEPTION: main
E/AndroidRuntime(10356): Process: app.alextran.immich.debug, PID: 10356
E/AndroidRuntime(10356): java.lang.ClassCastException: androidx.media3.datasource.HttpDataSource$InvalidResponseCodeException cannot be cast to java.lang.Error
E/AndroidRuntime(10356): 	at me.albemala.native_video_player.NativeVideoPlayerViewController.onPlayerError(NativeVideoPlayerViewController.kt:137)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl.lambda$updatePlaybackInfo$16(ExoPlayerImpl.java:2111)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl$$ExternalSyntheticLambda27.invoke(D8$$SyntheticClass:0)
E/AndroidRuntime(10356): 	at androidx.media3.common.util.ListenerSet$ListenerHolder.invoke(ListenerSet.java:342)
E/AndroidRuntime(10356): 	at androidx.media3.common.util.ListenerSet.lambda$queueEvent$0(ListenerSet.java:226)
E/AndroidRuntime(10356): 	at androidx.media3.common.util.ListenerSet$$ExternalSyntheticLambda1.run(D8$$SyntheticClass:0)
E/AndroidRuntime(10356): 	at androidx.media3.common.util.ListenerSet.flushEvents(ListenerSet.java:248)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl.updatePlaybackInfo(ExoPlayerImpl.java:2174)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl.handlePlaybackInfo(ExoPlayerImpl.java:2008)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl.lambda$new$1$androidx-media3-exoplayer-ExoPlayerImpl(ExoPlayerImpl.java:348)
E/AndroidRuntime(10356): 	at androidx.media3.exoplayer.ExoPlayerImpl$$ExternalSyntheticLambda10.run(D8$$SyntheticClass:0)
E/AndroidRuntime(10356): 	at android.os.Handler.handleCallback(Handler.java:991)
E/AndroidRuntime(10356): 	at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(10356): 	at android.os.Looper.loopOnce(Looper.java:232)
E/AndroidRuntime(10356): 	at android.os.Looper.loop(Looper.java:317)
E/AndroidRuntime(10356): 	at android.app.ActivityThread.main(ActivityThread.java:8787)
E/AndroidRuntime(10356): 	at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(10356): 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591)
E/AndroidRuntime(10356): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:871)
I/Process (10356): Sending signal. PID: 10356 SIG: 9

So it crashes here because HttpDataSource.InvalidResponseCodeException is a java.lang.Exception, not a java.lang.Error. The common super-class for both is java.lang.Throwable, and indeed changing the cast and the onError() parameter to Throwable solves the crash. The throbber (animated circle spinning while loading) is simply shown indefinitely and I can navigate to other images.

I'll send a PR for that to both upstream and the Immich fork.
EDIT: It's only in the code that was added to move to Exoplayer.

@rovo89 commented on GitHub (Jan 11, 2025): As for the crash in the video player, here is the trace with a debug build: ``` E/ExoPlayerImplInternal(10356): Playback error E/ExoPlayerImplInternal(10356): androidx.media3.exoplayer.ExoPlaybackException: Source error E/ExoPlayerImplInternal(10356): at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:737) E/ExoPlayerImplInternal(10356): at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:709) E/ExoPlayerImplInternal(10356): at android.os.Handler.dispatchMessage(Handler.java:105) E/ExoPlayerImplInternal(10356): at android.os.Looper.loopOnce(Looper.java:232) E/ExoPlayerImplInternal(10356): at android.os.Looper.loop(Looper.java:317) E/ExoPlayerImplInternal(10356): at android.os.HandlerThread.run(HandlerThread.java:85) E/ExoPlayerImplInternal(10356): Caused by: androidx.media3.datasource.HttpDataSource$InvalidResponseCodeException: Response code: 400 E/ExoPlayerImplInternal(10356): at androidx.media3.datasource.DefaultHttpDataSource.open(DefaultHttpDataSource.java:401) E/ExoPlayerImplInternal(10356): at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:87) E/ExoPlayerImplInternal(10356): at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1085) E/ExoPlayerImplInternal(10356): at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:450) E/ExoPlayerImplInternal(10356): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) E/ExoPlayerImplInternal(10356): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) E/ExoPlayerImplInternal(10356): at java.lang.Thread.run(Thread.java:1012) D/AndroidRuntime(10356): Shutting down VM E/AndroidRuntime(10356): FATAL EXCEPTION: main E/AndroidRuntime(10356): Process: app.alextran.immich.debug, PID: 10356 E/AndroidRuntime(10356): java.lang.ClassCastException: androidx.media3.datasource.HttpDataSource$InvalidResponseCodeException cannot be cast to java.lang.Error E/AndroidRuntime(10356): at me.albemala.native_video_player.NativeVideoPlayerViewController.onPlayerError(NativeVideoPlayerViewController.kt:137) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl.lambda$updatePlaybackInfo$16(ExoPlayerImpl.java:2111) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl$$ExternalSyntheticLambda27.invoke(D8$$SyntheticClass:0) E/AndroidRuntime(10356): at androidx.media3.common.util.ListenerSet$ListenerHolder.invoke(ListenerSet.java:342) E/AndroidRuntime(10356): at androidx.media3.common.util.ListenerSet.lambda$queueEvent$0(ListenerSet.java:226) E/AndroidRuntime(10356): at androidx.media3.common.util.ListenerSet$$ExternalSyntheticLambda1.run(D8$$SyntheticClass:0) E/AndroidRuntime(10356): at androidx.media3.common.util.ListenerSet.flushEvents(ListenerSet.java:248) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl.updatePlaybackInfo(ExoPlayerImpl.java:2174) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl.handlePlaybackInfo(ExoPlayerImpl.java:2008) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl.lambda$new$1$androidx-media3-exoplayer-ExoPlayerImpl(ExoPlayerImpl.java:348) E/AndroidRuntime(10356): at androidx.media3.exoplayer.ExoPlayerImpl$$ExternalSyntheticLambda10.run(D8$$SyntheticClass:0) E/AndroidRuntime(10356): at android.os.Handler.handleCallback(Handler.java:991) E/AndroidRuntime(10356): at android.os.Handler.dispatchMessage(Handler.java:102) E/AndroidRuntime(10356): at android.os.Looper.loopOnce(Looper.java:232) E/AndroidRuntime(10356): at android.os.Looper.loop(Looper.java:317) E/AndroidRuntime(10356): at android.app.ActivityThread.main(ActivityThread.java:8787) E/AndroidRuntime(10356): at java.lang.reflect.Method.invoke(Native Method) E/AndroidRuntime(10356): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591) E/AndroidRuntime(10356): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:871) I/Process (10356): Sending signal. PID: 10356 SIG: 9 ``` So it crashes [here](https://github.com/immich-app/native_video_player/blob/05ad661b338dad1a5cdac099630e6add351a708b/android/src/main/kotlin/me/albemala/native_video_player/NativeVideoPlayerViewController.kt#L137) because [HttpDataSource.InvalidResponseCodeException](https://developer.android.com/reference/androidx/media3/datasource/HttpDataSource.InvalidResponseCodeException) is a `java.lang.Exception`, not a `java.lang.Error`. The common super-class for both is `java.lang.Throwable`, and indeed changing the cast and the onError() parameter to Throwable solves the crash. The throbber (animated circle spinning while loading) is simply shown indefinitely and I can navigate to other images. I'll send a PR for that to ~~both upstream and~~ the Immich fork. EDIT: It's only in the code that was added to move to Exoplayer.
Author
Owner

@ckuyehar commented on GitHub (Jan 11, 2025):

For completeness, the download library has some code to accept untrusted certificates which calls exactly that method with an accept-all trust manager, indicating that it could profit from this approach as well.

The background_downloader does NOT allow self-signed certificates in release mode. I previously mentioned this in #15188.

However... even if you were to trust your CA and import your client certificate in your mobile device, the background_downloader will still fail. Why? The package background_downloader v8.9.0 doesn't support mutual TLS.

@ckuyehar commented on GitHub (Jan 11, 2025): > For completeness, the download library has [some code](https://github.com/781flyingdutchman/background_downloader/blob/main/android/src/main/kotlin/com/bbflight/background_downloader/Helpers.kt) to accept untrusted certificates which calls exactly that method with an accept-all trust manager, indicating that it could profit from this approach as well. The background_downloader does **NOT** [allow self-signed certificates in release mode.](https://github.com/781flyingdutchman/background_downloader/blob/e8c23b07783f353befc363f9182cc0471b4f63c4/lib/src/native_downloader.dart#L535-L538) I previously mentioned this in #15188. However... even if you were to trust your CA and import your client certificate in your mobile device, the background_downloader will still fail. Why? The package background_downloader v8.9.0 doesn't support mutual TLS.
Author
Owner

@rovo89 commented on GitHub (Jan 11, 2025):

The background_downloader does NOT allow self-signed certificates in release mode. I previously mentioned this in #15188.

I wasn't planning to use their debug feature. I mentioned it because it shows that their implementation seems to consider whatever is dictated by HttpsURLConnection.setDefaultSSLSocketFactory(). Calling the latter from Immich code should therefore have the same effect, and I have hopes that this approach would also allow to pass client certificates. See KeyManager in SSLContext.init().

@rovo89 commented on GitHub (Jan 11, 2025): > The background_downloader does **NOT** [allow self-signed certificates in release mode.](https://github.com/781flyingdutchman/background_downloader/blob/e8c23b07783f353befc363f9182cc0471b4f63c4/lib/src/native_downloader.dart#L535-L538) I previously mentioned this in #15188. I wasn't planning to use their debug feature. I mentioned it because it shows that their implementation seems to consider whatever is dictated by HttpsURLConnection.setDefaultSSLSocketFactory(). Calling the latter from Immich code should therefore have the same effect, and I have hopes that this approach would also allow to pass client certificates. See KeyManager in [SSLContext.init()](https://developer.android.com/reference/javax/net/ssl/SSLContext#init(javax.net.ssl.KeyManager[],%20javax.net.ssl.TrustManager[],%20java.security.SecureRandom)).
Author
Owner

@rovo89 commented on GitHub (Jan 11, 2025):

Yeah, my proof of concept for Android is working. 🥳 I added the following in ImmichApp.kt:

val keyStore = KeyStore.getInstance("PKCS12")
val clientCertificateContent = getAssets().open("mykey.pfx")
keyStore.load(clientCertificateContent, "mypassword".toCharArray())

val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, null)

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.keyManagers, null, SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)

I successfully tested videos and image downloads. That's for mTLS only. I see no problem accepting any server certificate, the question is whether that would need to be restricted to only the Immich server host like here. But maybe that could be done by checking the hostnames in the certificate... not sure.

Any comments on whether such an approach would be acceptable? Of course, the imported key would need to be used instead of assets (was just easier for testing) and it would have to be reloaded when the key is changed. Also no idea how I can create a dedicate native "component" for this stuff, I might need some assistance with that once the general idea has been confirmed. And sorry, no idea about iOS.

@rovo89 commented on GitHub (Jan 11, 2025): Yeah, my proof of concept for Android is working. 🥳 I added the following in ImmichApp.kt: ```kotlin val keyStore = KeyStore.getInstance("PKCS12") val clientCertificateContent = getAssets().open("mykey.pfx") keyStore.load(clientCertificateContent, "mypassword".toCharArray()) val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) keyManagerFactory.init(keyStore, null) val sslContext = SSLContext.getInstance("TLS") sslContext.init(keyManagerFactory.keyManagers, null, SecureRandom()) HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) ``` I successfully tested videos and image downloads. That's for mTLS only. I see no problem accepting any server certificate, the question is whether that would need to be restricted to only the Immich server host like [here](https://github.com/immich-app/immich/blob/cc6a8b0c74a9e16e9697eee967b86a80ea9a3b36/mobile/lib/utils/http_ssl_cert_override.dart#L56-L63). But maybe that could be done by checking the hostnames in the certificate... not sure. Any comments on whether such an approach would be acceptable? Of course, the imported key would need to be used instead of assets (was just easier for testing) and it would have to be reloaded when the key is changed. Also no idea how I can create a dedicate native "component" for this stuff, I might need some assistance with that once the general idea has been confirmed. And sorry, no idea about iOS.
Author
Owner

@ckuyehar commented on GitHub (Jan 11, 2025):

I successfully tested videos and image downloads.

🥳 congrats! awesome job.

I see no problem accepting any server certificate, the question is whether that would need to be restricted to only the Immich server host like here.

Immich should get out of the business of making that decision and defer that to Android/iOS. Let the OS perform the necessary checks.

Any comments on whether such an approach would be acceptable?

I do have some thoughts... Instead of maintaining/creating another certificate store within the Immich mobile app (abbrev IMA)... IMA should select the certificate from the OS. This will ensure we're using the OS certificate stores (trusted CAs and user) instead of a custom store within IMA. This will also allow IMA (when the time is right) to depreciate "allow self-signed certificates" because the CA would be trusted on the mobile device and a "self-signed" error would never appear.

In other words you should be able to trust the CA, uncheck "allow self-signed certificates" in IMA, select your client certificate in the Android User Store and login to IMA, download images and play videos without issue.

@ckuyehar commented on GitHub (Jan 11, 2025): > I successfully tested videos and image downloads. 🥳 congrats! awesome job. > I see no problem accepting any server certificate, the question is whether that would need to be restricted to only the Immich server host like [here](https://github.com/immich-app/immich/blob/cc6a8b0c74a9e16e9697eee967b86a80ea9a3b36/mobile/lib/utils/http_ssl_cert_override.dart#L56-L63). Immich should get out of the business of making that decision and defer that to Android/iOS. Let the OS perform the necessary checks. >Any comments on whether such an approach would be acceptable? I do have some thoughts... Instead of maintaining/creating another certificate store within the Immich mobile app (abbrev IMA)... IMA should select the certificate from the OS. This will ensure we're using the OS certificate stores (trusted CAs and user) instead of a custom store within IMA. This will also allow IMA (when the time is right) to depreciate "allow self-signed certificates" because the CA would be trusted on the mobile device and a "self-signed" error would never appear. In other words you should be able to trust the CA, uncheck "allow self-signed certificates" in IMA, select your client certificate in the Android User Store and login to IMA, download images and play videos without issue.
Author
Owner

@mertalev commented on GitHub (Jan 11, 2025):

cc @etnoy if you have any thoughts on this

@mertalev commented on GitHub (Jan 11, 2025): cc @etnoy if you have any thoughts on this
Author
Owner

@bo0tzz commented on GitHub (Jan 11, 2025):

Immich should get out of the business of making that decision and defer that to Android/iOS. Let the OS perform the necessary checks.

Flutter has its own certificate handling and doesn't use the system's. That's the whole reason we're here in the first place.

@bo0tzz commented on GitHub (Jan 11, 2025): > Immich should get out of the business of making that decision and defer that to Android/iOS. Let the OS perform the necessary checks. Flutter has its own certificate handling and doesn't use the system's. That's the whole reason we're here in the first place.
Author
Owner

@rovo89 commented on GitHub (Jan 11, 2025):

Exactly. It's neither my idea nor my preferred approach to upload the certificate in the Immich app, I'm just following the path that was already taken.

@rovo89 commented on GitHub (Jan 11, 2025): Exactly. It's neither my idea nor my preferred approach to upload the certificate in the Immich app, I'm just following the path that was already taken.
Author
Owner

@rovo89 commented on GitHub (Jan 11, 2025):

https://github.com/dart-lang/sdk/issues/50435 sounds like no real solution is planned for the HTTP client in Dart, they suggest using package:cronet_http. No idea about that, I'm not familiar with Dart. Indeed it feels a bit clunky having to upload the certificates again that I already configured in the OS, but it's a one-time thing, so it's acceptable for me as a user with a special setup.

I also thought about adding the self-signed certificate to the trust store, which I would anyway do in the OS, but using it from there has the same limitations as above and I'm not sure if yet another upload button in Immich would be worth it. So the simple switch sounds like an acceptable compromise to me.

@rovo89 commented on GitHub (Jan 11, 2025): https://github.com/dart-lang/sdk/issues/50435 sounds like no real solution is planned for the HTTP client in Dart, they suggest using `package:cronet_http`. No idea about that, I'm not familiar with Dart. Indeed it feels a bit clunky having to upload the certificates again that I already configured in the OS, but it's a one-time thing, so it's acceptable for me as a user with a special setup. I also thought about adding the self-signed certificate to the trust store, which I would anyway do in the OS, but using it from there has the same limitations as above and I'm not sure if yet another upload button in Immich would be worth it. So the simple switch sounds like an acceptable compromise to me.
Author
Owner

@LordArrin commented on GitHub (Jan 29, 2025):

Right now, the system looks like so overloaded with all of it security stuff, it's no wonder something breaks. I'll join the commentators above, all apps on device MUST trust system CA, it just built for it. Some strange decisions like SSL Pinning can be acceptable in banking or payment apps, but not in userspace.

@LordArrin commented on GitHub (Jan 29, 2025): Right now, the system looks like so overloaded with all of it security stuff, it's no wonder something breaks. I'll join the commentators above, all apps on device MUST trust system CA, it just built for it. Some strange decisions like SSL Pinning can be acceptable in banking or payment apps, but not in userspace.
Author
Owner

@LordArrin commented on GitHub (Jan 29, 2025):

And yep, I use Immich behind nginx with my personal CA's, and literally all works amasing, but not a video playback and downloading images from Immich on Android. From desktop browsers, android browsers all of it works like charm.

@LordArrin commented on GitHub (Jan 29, 2025): And yep, I use Immich behind nginx with my personal CA's, and literally all works amasing, but not a video playback and downloading images from Immich on Android. From desktop browsers, android browsers all of it works like charm.
Author
Owner

@rovo89 commented on GitHub (Jan 29, 2025):

While I generally agree that apps should use the system SSL stack, the reason why this is not the case is in the platform abstraction of Dart, not in the immich app itself. As document above, Dart doesn't use the Android HTTPS client, but rolls its own based on OpenSSL directly.

I don't think switching away from Dart is an option. 😉 There are some ideas to add support for private keys from the Android keystore (which can't be accessed as bytes, the system will take care of encryption): https://github.com/dart-lang/http/issues/1237. Or it has been mentioned in https://github.com/dart-lang/sdk/issues/50435 to use package:cronet_http instead.

But all of these options are either just ideas/wishes without any commitment and timeline, or they would have a bigger impact on the app itself. As it has been made clear that use-cases such as mTLS have low priority, I can't imagine that we'll see such changes anytime soon.

On the other hand, the Immich app does already have options to upload the client certificate. Yes, it's redundant, but for me as a user/admin, that's much better than other workarounds I'd have to implement in the current state (e.g. add another domain without mTLS), or not being able to watch remote videos at all.

The approach I mentioned in https://github.com/immich-app/immich/issues/15230#issuecomment-2584938841 is fairly low impact, not much code (= less to maintain) and doesn't add any further user-facing workarounds. It also matches the semantics of the existing codes to globally enable the client certificate - just for a different HTTP client.

That's why I'd still like to work on implementing this properly, and I hope to get some agreement from the core devs that such a PR would be merged once it's ready. Can I get that?

@rovo89 commented on GitHub (Jan 29, 2025): While I generally agree that apps *should* use the system SSL stack, the reason why this is not the case is in the platform abstraction of Dart, not in the immich app itself. As document above, Dart doesn't use the Android HTTPS client, but rolls its own based on OpenSSL directly. I don't think switching away from Dart is an option. 😉 There are some ideas to add support for private keys from the Android keystore (which can't be accessed as bytes, the system will take care of encryption): https://github.com/dart-lang/http/issues/1237. Or it has been mentioned in https://github.com/dart-lang/sdk/issues/50435 to use `package:cronet_http` instead. But all of these options are either just ideas/wishes without any commitment and timeline, or they would have a bigger impact on the app itself. As it has been made clear that use-cases such as mTLS have low priority, I can't imagine that we'll see such changes anytime soon. On the other hand, the Immich app does already have options to upload the client certificate. Yes, it's redundant, but for me as a user/admin, that's much better than other workarounds I'd have to implement in the current state (e.g. add another domain without mTLS), or not being able to watch remote videos at all. The approach I mentioned in https://github.com/immich-app/immich/issues/15230#issuecomment-2584938841 is fairly low impact, not much code (= less to maintain) and doesn't add any further user-facing workarounds. It also matches the semantics of the existing codes to globally enable the client certificate - just for a different HTTP client. That's why I'd still like to work on implementing this properly, and I hope to get some agreement from the core devs that such a PR would be merged once it's ready. Can I get that?
Author
Owner

@LordArrin commented on GitHub (Jan 29, 2025):

Find a temporary solution. I enabled LSPosed module SSLUnpinning for Immich, relaunch app and all videos plays like it may do.

@LordArrin commented on GitHub (Jan 29, 2025): Find a temporary solution. I enabled LSPosed module [SSLUnpinning](https://github.com/Xposed-Modules-Repo/io.github.tehcneko.sslunpinning) for Immich, relaunch app and all videos plays like it may do.
Author
Owner

@rovo89 commented on GitHub (Jan 29, 2025):

Interesting to see that variants of Xposed are still around, I'm the original author 😉 But nowadays I don't even root my phone anymore, which is the prerequisite.

Besides that, this method might help to disable SSL validation globally (which is a very big hammer), but I don't see how it would make mTLS possible. The private key of the client certificate should be stored in separate hardware, unaccessible even for the Android core, and encryption takes place via IPC as far as I understood.

@rovo89 commented on GitHub (Jan 29, 2025): Interesting to see that variants of Xposed are still around, I'm the original author 😉 But nowadays I don't even root my phone anymore, which is the prerequisite. Besides that, this method might help to disable SSL validation globally (which is a very big hammer), but I don't see how it would make mTLS possible. The private key of the client certificate should be stored in separate hardware, unaccessible even for the Android core, and encryption takes place via IPC as far as I understood.
Author
Owner

@etnoy commented on GitHub (Jan 29, 2025):

Interesting to see that variants of Xposed are still around, I'm the original author 😉 But nowadays I don't even root my phone anymore, which is the prerequisite.

Besides that, this method might help to disable SSL validation globally (which is a very big hammer), but I don't see how it would make mTLS possible. The private key of the client certificate should be stored in separate hardware, unaccessible even for the Android core, and encryption takes place via IPC as far as I understood.

I have nothing to contribute to this issue, but I just want to thank you for Xposed. It's been a few years but I remember those wild days fondly :)

@etnoy commented on GitHub (Jan 29, 2025): > Interesting to see that variants of Xposed are still around, I'm the original author 😉 But nowadays I don't even root my phone anymore, which is the prerequisite. > > Besides that, this method might help to disable SSL validation globally (which is a very big hammer), but I don't see how it would make mTLS possible. The private key of the client certificate should be stored in separate hardware, unaccessible even for the Android core, and encryption takes place via IPC as far as I understood. I have nothing to contribute to this issue, but I just want to thank you for Xposed. It's been a few years but I remember those wild days fondly :)
Author
Owner

@polomani commented on GitHub (Feb 5, 2025):

@rovo89

I'd have to implement in the current state (e.g. add another domain without mTLS)

Having multiple domains for the app might not work as in that case you'd need to work around the authentication (at least for the web version, it uses cookies, which aren't easily shared between domains).

After some trial and error, I solved it on my setup using HAProxy (it is most likely possible with other reverse proxies, but I needed something with low CPU/RAM consumption).

Here I first define verify optional which will validate the client certificate only if it is available.
Later in the code it checks the URL path, and lets the request through without the client cert only for the video playback endpoints.
Basically, mTLS is required on everything else, except the problematic video playback endpoint.

haproxy.cfg:

    global
      log stdout format raw local0

    defaults
      mode http

    frontend https-in
      # tls-certs should contain two files tls.crt, tls.crt.key (issued by trusted authority)
      # ca.crt is a self-signed CA for mTLS
      bind *:8443 ssl crt /etc/haproxy/tls-certs/ ca-file /etc/haproxy/mtls-ca/ca.crt verify optional

      acl public_video_playback path_reg ^/api/assets/[a-f0-9\-]+/video/playback
      acl client_has_cert ssl_c_used

      # Require mTLS for everything EXCEPT video playback
      http-request deny if !client_has_cert !public_video_playback

      use_backend backend_servers

    backend backend_servers
      server immich-server 127.0.0.1:2283 check # this might need to be adjusted based on setup

This is slightly not ideal, but if there is no proper fix, allows us to keep using mTLS setup and all is behind one domain / ip.
IMHO mutual TLS is a robust, secure, and reliable way for people to expose their services to the internet and should be a recommended approach. Much better then compared to relying on VPNs like Tailscale (as then it can't even be considered as pure self-hosting anymore :) ).

@polomani commented on GitHub (Feb 5, 2025): @rovo89 > I'd have to implement in the current state (e.g. add another domain without mTLS) Having multiple domains for the app might not work as in that case you'd need to work around the authentication (at least for the web version, it uses cookies, which aren't easily shared between domains). After some trial and error, I solved it on my setup using HAProxy (it is most likely possible with other reverse proxies, but I needed something with low CPU/RAM consumption). Here I first define `verify optional` which will validate the client certificate only if it is available. Later in the code it checks the URL path, and lets the request through without the client cert only for the video playback endpoints. Basically, mTLS is required on everything else, except the problematic video playback endpoint. haproxy.cfg: ``` global log stdout format raw local0 defaults mode http frontend https-in # tls-certs should contain two files tls.crt, tls.crt.key (issued by trusted authority) # ca.crt is a self-signed CA for mTLS bind *:8443 ssl crt /etc/haproxy/tls-certs/ ca-file /etc/haproxy/mtls-ca/ca.crt verify optional acl public_video_playback path_reg ^/api/assets/[a-f0-9\-]+/video/playback acl client_has_cert ssl_c_used # Require mTLS for everything EXCEPT video playback http-request deny if !client_has_cert !public_video_playback use_backend backend_servers backend backend_servers server immich-server 127.0.0.1:2283 check # this might need to be adjusted based on setup ``` This is slightly not ideal, but if there is no proper fix, allows us to keep using mTLS setup and all is behind one domain / ip. IMHO mutual TLS is a robust, secure, and reliable way for people to expose their services to the internet and should be a recommended approach. Much better then compared to relying on VPNs like Tailscale (as then it can't even be considered as pure self-hosting anymore :) ).
Author
Owner

@rovo89 commented on GitHub (Feb 28, 2025):

I drafted PR #16403 to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first.

@rovo89 commented on GitHub (Feb 28, 2025): I drafted PR #16403 to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first.
Author
Owner

@pedropombeiro commented on GitHub (Feb 28, 2025):

I drafted PR #16403 to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first.

@rovo89 I can confirm that the problem also affects iOS.

@pedropombeiro commented on GitHub (Feb 28, 2025): > I drafted PR [#16403](https://github.com/immich-app/immich/pull/16403) to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first. @rovo89 I can confirm that the problem also affects iOS.
Author
Owner

@shenlong-tanwen commented on GitHub (Mar 3, 2025):

I drafted PR #16403 to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first.

The changes required for cronet_http should be rather simple. We should test it a lot to make sure it is stable and that nothing breaks. However, AFAIK, using it will solve the issue only with the network calls made from dart but not the one made from the native side like the video player or the background downloads.

@shenlong-tanwen commented on GitHub (Mar 3, 2025): > I drafted PR [#16403](https://github.com/immich-app/immich/pull/16403) to support remote video playback and asset downloads with mTLS. Android-only, not sure if this problem even exists on iOS. It shouldn't be too hard to add support for self-signed certificates, but I want to get some feedback first. The changes required for `cronet_http` should be rather simple. We should test it a lot to make sure it is stable and that nothing breaks. However, AFAIK, using it will solve the issue only with the network calls made from dart but not the one made from the native side like the video player or the background downloads.
Author
Owner

@rovo89 commented on GitHub (Mar 4, 2025):

However, AFAIK, using it will solve the issue only with the network calls made from dart but not the one made from the native side like the video player or the background downloads.

Now that you say it, that makes sense... In other words, using cronet_http might make the current workarounds more elegant, but it won't solve the open issues.

"More elegant" means that self-signed certificates could be imported into the Android trust store and would be automatically accepted. Client certificates could be imported into the Android keystore, however an app must bring up the dialog to choose the key and grant permission to it. So some special code would still be required, and I'm not sure whether all required interfaces are exposed to Dart. I assume that additional code would be required for the video player and downloads.

I have no idea which timeline would be realistic for a switch - if it happens at all. So from my point of view, it make sense to move forward with the PR. I'm currently trying to enhance it for self-signed certificates. The main difficulty is to accept them only for the Immich host...

@rovo89 commented on GitHub (Mar 4, 2025): > However, AFAIK, using it will solve the issue only with the network calls made from dart but not the one made from the native side like the video player or the background downloads. Now that you say it, that makes sense... In other words, using `cronet_http` might make the current workarounds more elegant, but it won't solve the open issues. "More elegant" means that self-signed certificates could be imported into the Android trust store and would be automatically accepted. Client certificates could be imported into the Android keystore, however an app must bring up the dialog to choose the key and grant permission to it. So some special code would still be required, and I'm not sure whether all required interfaces are exposed to Dart. I assume that additional code would be required for the video player and downloads. I have no idea which timeline would be realistic for a switch - if it happens at all. So from my point of view, it make sense to move forward with the PR. I'm currently trying to enhance it for self-signed certificates. The main difficulty is to accept them only for the Immich host...
Author
Owner

@Trezamere commented on GitHub (Mar 9, 2025):

On the other hand, the Immich app does already have options to upload the client certificate.

This isn't really an acceptable solution, it needs to allow CAs, rotating your client certs regularly is a basic and common best practice and Caddy (and others) do it for free. I've had to "Allow self-signed SSL certificates" which is not ideal to even connect to the server, it's strange that even with that though you still can't download remote assets, it should just be ignoring it.

@Trezamere commented on GitHub (Mar 9, 2025): > On the other hand, the Immich app does already have options to upload the client certificate. This isn't really an acceptable solution, it _needs_ to allow CAs, rotating your client certs regularly is a basic and common best practice and Caddy (and others) do it for free. I've had to "Allow self-signed SSL certificates" which is not ideal to even connect to the server, it's strange that even with that though you still can't download remote assets, it should just be ignoring it.
Author
Owner

@rovo89 commented on GitHub (Mar 10, 2025):

rotating your client certs regularly is a basic and common best practice and Caddy (and others) do it for free

Are you sure you're talking about client certificates here? Everything in your comment sounds like you mean server certificates instead. The former is for authentication, proofing that you are a certain user (comparable to a password), whereas the latter is how the server proofs that it's the one you wanted to talk to and not some man-in-the-middle which intercepted your traffic.

The import button is only meant for client certificates. If you upload a server certificate instead, you might not get an error because the format is the same. But it won't help anything because the certificate will only be used when the server asks for it, and it won't be used to check the server's authenticity. The "Allow self-signed SSL certificates" on the other hand is only for server certificates, because obviously there can't be a toggle in the app to tell the server to trust your client certificate.

As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate. That can even work for installations in your home network because you can proof ownership via DNS, in which case you can also get a wildcard certificate for your whole domain (*.example.org).

it's strange that even with that though you still can't download remote assets, it should just be ignoring it.

Exactly that issue would be solved on Android with #16403, for both client certificates and self-signed server certificates.

@rovo89 commented on GitHub (Mar 10, 2025): > rotating your client certs regularly is a basic and common best practice and Caddy (and others) do it for free Are you sure you're talking about *client* certificates here? Everything in your comment sounds like you mean *server* certificates instead. The former is for authentication, proofing that you are a certain user (comparable to a password), whereas the latter is how the server proofs that it's the one you wanted to talk to and not some man-in-the-middle which intercepted your traffic. The import button is only meant for client certificates. If you upload a server certificate instead, you might not get an error because the format is the same. But it won't help anything because the certificate will only be used when the server asks for it, and it won't be used to check the server's authenticity. The "Allow self-signed SSL certificates" on the other hand is only for server certificates, because obviously there can't be a toggle in the app to tell the server to trust your client certificate. As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate. That can even work for installations in your home network because you can proof ownership via DNS, in which case you can also get a wildcard certificate for your whole domain (*.example.org). > it's strange that even with that though you still can't download remote assets, it should just be ignoring it. Exactly that issue would be solved on Android with #16403, for both client certificates and self-signed server certificates.
Author
Owner

@Trezamere commented on GitHub (Mar 10, 2025):

Are you sure you're talking about client certificates here? Everything in your comment sounds like you mean server certificates instead... The import button is only meant for client certificates.

Ahh, you're right, in my frustration trying to get this working I overlooked that it was specifically a client certificate (you even said as much 🤦). I assumed it was for uploading a server cert as it's unusual to see built-in client certs/mTLS in these hobby projects IME.

As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate.

While certainly convenient, I think it's a stretch to say using a public CA is a 'best practice'; there are many use-cases for leveraging self-signed certs. Undoubtedly wildcard certificates are not included in best practices.

In any event thank you for putting that MR together and hopefully that can get merged soon™, will keep an eye on it.

@Trezamere commented on GitHub (Mar 10, 2025): > Are you sure you're talking about client certificates here? Everything in your comment sounds like you mean server certificates instead... The import button is only meant for client certificates. Ahh, you're right, in my frustration trying to get this working I overlooked that it was specifically a client certificate (you even said as much 🤦). I assumed it was for uploading a server cert as it's unusual to see built-in client certs/mTLS in these hobby projects IME. > As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate. While certainly convenient, I think it's a stretch to say using a public CA is a 'best practice'; there are many use-cases for leveraging self-signed certs. Undoubtedly wildcard certificates are not included in best practices. In any event thank you for putting that MR together and hopefully that can get merged soon™, will keep an eye on it.
Author
Owner

@Fmstrat commented on GitHub (Apr 28, 2025):

Exactly that issue would be solved on Android with https://github.com/immich-app/immich/pull/16403, for both client certificates and self-signed server certificates.

@rovo89 I was directed here, then read through the chat in the above PR, and wondering if you could provide some clarity for me since I'm apparently just dumb today.

Given the comment about self-signed server certificates, does this mean 16403 would allow for importing a CA and mTLS, or just mTLS?

Because for us...

As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate.

This isn't best practice for all threat models. Public CAs are not "trusted", but an internal enterprise one, is.

@Fmstrat commented on GitHub (Apr 28, 2025): > Exactly that issue would be solved on Android with https://github.com/immich-app/immich/pull/16403, for both client certificates and self-signed server certificates. @rovo89 I was directed here, then read through the chat in the above PR, and wondering if you could provide some clarity for me since I'm apparently just dumb today. Given the comment about self-signed server certificates, does this mean 16403 would allow for importing a CA and mTLS, or just mTLS? Because for us... > As you mention best practices - the much better way would be getting a domain and using Let's Encrypt to get a valid, trusted server certificate. This isn't best practice for all threat models. Public CAs are not "trusted", but an internal enterprise one, is.
Author
Owner

@rovo89 commented on GitHub (Apr 28, 2025):

I didn't add any new settings, my PR "only" applies the existing settings to the remote video player and media downloads on Android, i.e. those parts which don't use Dart's HTTP wrapper.

The existing settings allow you to upload a client certificate for mTLS and you have a switch to trust all server certificates. I think the hostname must still match and probably validity date as well.

I understand that there might be scenarios where that big hammer is too much, but uploading a trusted server CA certificate is out of scope for my PR. If someone works on that, it will certainly be easier for them to apply this to non-Dart after my PR has been merged.

By the way, I made the comment about best practices after someone suggested that it's common to regularly rotate client certificates and that the app should have a way to handle that. To me, that sounded much less common than someone using Let's Encrypt to get a server certificate for their publicly reachable reverse proxy. "Best" practice is probably not the right word, as it depends on the individual situation, so read it as "which setup fits for many people".

@rovo89 commented on GitHub (Apr 28, 2025): I didn't add any new settings, my PR "only" applies the existing settings to the remote video player and media downloads on Android, i.e. those parts which don't use Dart's HTTP wrapper. The existing settings allow you to upload a client certificate for mTLS and you have a switch to trust *all* server certificates. I think the hostname must still match and probably validity date as well. I understand that there might be scenarios where that big hammer is too much, but uploading a trusted server CA certificate is out of scope for my PR. If someone works on that, it will certainly be easier for them to apply this to non-Dart after my PR has been merged. By the way, I made the comment about best practices after someone suggested that it's common to regularly rotate *client* certificates and that the app should have a way to handle that. To me, that sounded much less common than someone using Let's Encrypt to get a server certificate for their publicly reachable reverse proxy. "Best" practice is probably not the right word, as it depends on the individual situation, so read it as "which setup fits for many people".
Author
Owner

@Fmstrat commented on GitHub (Apr 29, 2025):

@rovo89 Thanks for the feedback, that helps. Could we instead make Immich just trust the CAs that are installed on the Android device? For instance, ntfy does this by adding this line to Android Manifest:

31eadd9768/app/src/main/AndroidManifest.xml (L31)

And then a network config file of: https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/xml/network_security_config.xml

This file is what allows user-defined CAs to be validated in the app. A few lines of XML changes may be all that's required.

Note, cleartextTrafficPermitted in that network config just means HTTP can be used alongside HTTPS (default is both) as that true can come across as a scary setting.

@Fmstrat commented on GitHub (Apr 29, 2025): @rovo89 Thanks for the feedback, that helps. Could we instead make Immich just trust the CAs that are installed on the Android device? For instance, `ntfy` does this by adding this line to Android Manifest: https://github.com/binwiederhier/ntfy-android/blob/31eadd9768988202c457afcc4aa752beb1aecab8/app/src/main/AndroidManifest.xml#L31 And then a network config file of: https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/xml/network_security_config.xml This file is what allows user-defined CAs to be validated in the app. A few lines of XML changes may be all that's required. Note, `cleartextTrafficPermitted` in that network config just means HTTP can be used alongside HTTPS (default is both) as that `true` can come across as a scary setting.
Author
Owner

@rovo89 commented on GitHub (Apr 29, 2025):

A few lines of XML changes may be all that's required.

... if Dart was using the Android HTTP wrapper. But it isn't, it comes with its own wrapper which uses OpenSSL directly. I did quite some analysis before the PR which you can read in https://github.com/immich-app/immich/issues/15230#issuecomment-2584057446, although it was for the other way around.

For the Android "native" implementation that I added, such configuration might be enough, but for the 95% of the app which use Dart, it would mean having to replace the whole HTTP stack.

@rovo89 commented on GitHub (Apr 29, 2025): > A few lines of XML changes may be all that's required. ... if Dart was using the Android HTTP wrapper. But it isn't, it comes with its own wrapper which uses OpenSSL directly. I did quite some analysis before the PR which you can read in https://github.com/immich-app/immich/issues/15230#issuecomment-2584057446, although it was for the other way around. For the Android "native" implementation that I added, such configuration might be enough, but for the 95% of the app which use Dart, it would mean having to replace the whole HTTP stack.
Author
Owner

@vekexasia commented on GitHub (May 20, 2025):

As reported in #18396, after setting up mtls certificate and verify that it is working properly i cannot configure the automatic url switching cause the external url gives me a red exclamation mark.

@vekexasia commented on GitHub (May 20, 2025): As reported in #18396, after setting up mtls certificate and verify that it is working properly i cannot configure the automatic url switching cause the external url gives me a red exclamation mark.
Author
Owner

@amigthea commented on GitHub (May 22, 2025):

unfortunately, even with the new pr, having both server and app on v1.133 does not resolve the video playback on mTLS for me

can I provide some logs or what to help troubleshooting?

@amigthea commented on GitHub (May 22, 2025): unfortunately, even with the [new pr](https://github.com/immich-app/immich/pull/16403), having both server and app on v1.133 does not resolve the video playback on mTLS for me can I provide some logs or what to help troubleshooting?
Author
Owner

@rovo89 commented on GitHub (May 22, 2025):

Are you on Android and mTLS works for photos? You can try to provide logs (logcat probably), maybe they contain something strange. You don't happen to have something like this in the Immich logs? https://github.com/immich-app/immich/pull/16403#issuecomment-2779820363

@rovo89 commented on GitHub (May 22, 2025): Are you on Android and mTLS works for photos? You can try to provide logs (logcat probably), maybe they contain something strange. You don't happen to have something like this in the Immich logs? https://github.com/immich-app/immich/pull/16403#issuecomment-2779820363
Author
Owner

@amigthea commented on GitHub (May 22, 2025):

I'm on android and mTLS did not give me problems with photos, even before the 1.133 fix.

this are the immich app log on mTLS, the https://github.com/immich-app/immich/pull/16403#issuecomment-2779820363 error does not shows up but there's another one


2025-05-22 16:14:33.505121 | info     | AssetNotifier        | Load assets: 1002ms |
2025-05-22 16:14:33.505040 | info     | AlbumService         | Syncing completed. Changes: false |
2025-05-22 16:14:33.505025 | info     | SyncService          | Local album Camera has not changed. Skipping sync. |
2025-05-22 16:14:33.444517 | info     | SyncService          | Syncing a local album to DB: Camera |
2025-05-22 16:14:33.443969 | info     | AlbumService         | 'Recents' is not selected, keeping only selected albums |
2025-05-22 16:14:33.443951 | info     | AlbumService         | Found 21 device albums |
2025-05-22 16:14:32.470132 | severe   | AuthenticationNotifier | Error getting user information from the server [CATCH ALL] | ParallelWaitError: Null check operator used on a null value |
#0      UserPreferencesResponseDto.fromJson (package:openapi/model/user_preferences_response_dto.dart:102)
#1      ApiClient.fromJson (package:openapi/api_client.dart:660)
#2      ApiClient.deserialize (package:openapi/api_client.dart:158)
<asynchronous suspension>
#3      UsersApi.getMyPreferences (package:openapi/api/users_api.dart:177)
<asynchronous suspension>
#4      FutureRecord2.wait.<anonymous closure> (dart:async/future_extensions.dart:88)
<asynchronous suspension>
#5      UserApiRepository.getMyUser (package:immich_mobile/infrastructure/repositories/user_api.repository.dart:17)
<asynchronous suspension>
#6      UserService.refreshMyUser (package:immich_mobile/domain/services/user.service.dart:38)
<asynchronous suspension>
#7      Future.timeout.<anonymous closure> (dart:async/future_impl.dart:1043)
<asynchronous suspension>
#8      AuthNotifier.saveAuthInfo (package:immich_mobile/providers/auth.provider.dart:114)
<asynchronous suspension>
#9      SplashScreenPageState.resumeSession (package:immich_mobile/pages/common/splash_screen.page.dart:49)
<asynchronous suspension>
#10     Future._propagateToListeners.handleWhenCompleteCallback.<anonymous closure> (dart:async/future_impl.dart:918)
<asynchronous suspension>

2025-05-22 16:14:32.242410 | info     | HttpSSLCertOverride  | Setting client certificate |

when I'm trying to play video it remains stuck on a frame, with the immich loading icon spinning in the center.

I'm doing something wrong?

@amigthea commented on GitHub (May 22, 2025): I'm on android and mTLS did not give me problems with photos, even before the 1.133 fix. this are the immich app log on mTLS, the https://github.com/immich-app/immich/pull/16403#issuecomment-2779820363 error does not shows up but there's another one ``` 2025-05-22 16:14:33.505121 | info | AssetNotifier | Load assets: 1002ms | 2025-05-22 16:14:33.505040 | info | AlbumService | Syncing completed. Changes: false | 2025-05-22 16:14:33.505025 | info | SyncService | Local album Camera has not changed. Skipping sync. | 2025-05-22 16:14:33.444517 | info | SyncService | Syncing a local album to DB: Camera | 2025-05-22 16:14:33.443969 | info | AlbumService | 'Recents' is not selected, keeping only selected albums | 2025-05-22 16:14:33.443951 | info | AlbumService | Found 21 device albums | 2025-05-22 16:14:32.470132 | severe | AuthenticationNotifier | Error getting user information from the server [CATCH ALL] | ParallelWaitError: Null check operator used on a null value | #0 UserPreferencesResponseDto.fromJson (package:openapi/model/user_preferences_response_dto.dart:102) #1 ApiClient.fromJson (package:openapi/api_client.dart:660) #2 ApiClient.deserialize (package:openapi/api_client.dart:158) <asynchronous suspension> #3 UsersApi.getMyPreferences (package:openapi/api/users_api.dart:177) <asynchronous suspension> #4 FutureRecord2.wait.<anonymous closure> (dart:async/future_extensions.dart:88) <asynchronous suspension> #5 UserApiRepository.getMyUser (package:immich_mobile/infrastructure/repositories/user_api.repository.dart:17) <asynchronous suspension> #6 UserService.refreshMyUser (package:immich_mobile/domain/services/user.service.dart:38) <asynchronous suspension> #7 Future.timeout.<anonymous closure> (dart:async/future_impl.dart:1043) <asynchronous suspension> #8 AuthNotifier.saveAuthInfo (package:immich_mobile/providers/auth.provider.dart:114) <asynchronous suspension> #9 SplashScreenPageState.resumeSession (package:immich_mobile/pages/common/splash_screen.page.dart:49) <asynchronous suspension> #10 Future._propagateToListeners.handleWhenCompleteCallback.<anonymous closure> (dart:async/future_impl.dart:918) <asynchronous suspension> 2025-05-22 16:14:32.242410 | info | HttpSSLCertOverride | Setting client certificate | ``` when I'm trying to play video it remains stuck on a frame, with the immich loading icon spinning in the center. I'm doing something wrong?
Author
Owner

@amonhk commented on GitHub (May 22, 2025):

I also have the same problem with mTLS (self-signed) using the latest Android client and server (v1.133).
When on-device content is deleted, downloads of images and videos from the server fail.
(server uses a custom port, not 443)

@amonhk commented on GitHub (May 22, 2025): I also have the same problem with mTLS (self-signed) using the latest Android client and server (v1.133). When on-device content is deleted, downloads of images and videos from the server fail. (server uses a custom port, not 443)
Author
Owner

@rovo89 commented on GitHub (May 22, 2025):

The custom port is an interesting detail, although as far as I can see only the hostname is checked (as it should be). Could you check if adb logcat shows anything unexpected?

@rovo89 commented on GitHub (May 22, 2025): The custom port is an interesting detail, although as far as I can see only the hostname is checked (as it should be). Could you check if `adb logcat` shows anything unexpected?
Author
Owner

@amigthea commented on GitHub (May 22, 2025):

right know I cannot use adb logcat because I use mTLS only for public connection and it's bypassed for LAN connection. If no one else can provide them I will bring up a test environment asap

@amigthea commented on GitHub (May 22, 2025): right know I cannot use `adb logcat` because I use mTLS only for public connection and it's bypassed for LAN connection. If no one else can provide them I will bring up a test environment asap
Author
Owner

@amigthea commented on GitHub (May 22, 2025):

I was able to find a faster workaround, here's the logcat

05-22 18:50:35.831  9764  9764 W WindowOnBackDispatcher: OnBackInvokedCallback is not enabled for the application.
05-22 18:50:35.831  9764  9764 W WindowOnBackDispatcher: Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
05-22 18:50:35.834  9764  9764 D ViewRootImplExtImpl: wrapConfigInfoIntoFlags rotation=0, smallestScreenWidthDp=411, relayoutAsync=true, newFlags=26935296, title=app.alextran.immich/app.alextran.immich.MainActivity
05-22 18:50:35.834  9764  9764 D VRI[MainActivity]: relayoutWindow result, sizeChanged:false, surfaceControlChanged:false, transformHintChanged:false, mSurfaceSize:Point(1080, 2412), mLastSurfaceSize:Point(1080, 2412), mWidth:1080, mHeight:2412, requestedWidth:1080, requestedHeight:2412, transformHint:0, lastTransformHint:0, installOrientation:0, displayRotation:0, isSurfaceValid:true, attr.flag:-2122252032, useBlast:true, tmpFrames:ClientWindowFrames{frame=[0,0][1080,2412] display=[0,0][1080,2412] parentFrame=[0,0][1080,2412]}
05-22 18:50:35.834  9764  9764 W VRI[MainActivity]: updateBlastSurfaceIfNeeded, surfaceSize:Point(1080, 2412), lastSurfaceSize:Point(1080, 2412), format:-3, blastBufferQueue:android.graphics.BLASTBufferQueue@91f3488
05-22 18:50:35.835  9764  9764 D OplusWindowManager: get WMS extension: android.os.BinderProxy@c1113e6
05-22 18:50:35.841  9764  9764 I SurfaceControl:  setExtendedRangeBrightness sc=Surface(name=app.alextran.immich/app.alextran.immich.MainActivity)/@0x44ccfb8,currentBufferRatio=1.0,desiredRatio=1.0
05-22 18:50:35.866  9764  9764 I ExoPlayerImpl: Init 826a140 [AndroidXMedia3/1.5.0] [OnePlus9Pro, LE2123, OnePlus, 34]
05-22 18:50:35.870  9764  9764 W AidlConversionCppNdk: aidl2legacy_AudioChannelLayout_audio_channel_mask_t: no legacy output audio_channel_mask_t found for AudioChannelLayout{layoutMask: 16}
05-22 18:50:35.886  9764  9764 I Quality : Skipped: false 5 cost 44.21756 refreshRate 8287800 bit true processName app.alextran.immich
05-22 18:50:35.887  9764  9764 W VRI[MainActivity]: handleResized abandoned!
05-22 18:50:36.108  9764  9764 D CompatibilityChangeReporter: Compat change id reported: 236825255; UID 10573; state: ENABLED
05-22 18:50:36.108  9764  9764 D BufferQueueConsumer: [](id:262400000002,api:0,p:-1,c:9764) connect: controlledByApp=true
05-22 18:50:36.109  9764  9764 D BufferQueueProducer: [SurfaceView[app.alextran.immich/app.alextran.immich.MainActivity]#1(BLAST Consumer)1](id:262400000001,api:1,p:9764,c:9764) disconnect: api 1
05-22 18:50:36.109  9764  9764 D BufferQueueProducer: [ImageReader-1080x2412f1m3-9764-0](id:262400000002,api:1,p:9764,c:9764) connect: api=1 producerControlledByApp=true
05-22 18:50:36.110  9764  9764 I SurfaceView: 51902665 onAttachedToWindow
05-22 18:50:36.118  9764  9764 D BufferQueueConsumer: [](id:262400000003,api:0,p:-1,c:9764) connect: controlledByApp=false
05-22 18:50:36.118  9764  9764 I SurfaceView: 51902665 visibleChanged -- surfaceCreated
05-22 18:50:36.118  9764  9764 D SurfaceView: 51902665 handleSyncNoBuffer
05-22 18:50:36.119  9764  9764 D SurfaceView: 51902665 finishedDrawing
05-22 18:50:36.119  9764  9764 D SurfaceView: 51902665 performDrawFinished mAttachedToWindow true
05-22 18:50:36.119  9764  9764 D SurfaceView: 51902665 draw mDrawFinished true isAboveParent false (mPrivateFlags & PFLAG_SKIP_DRAW) 0
05-22 18:50:36.119  9764  9764 D SurfaceView: 51902665 clearSurfaceViewPort mCornerRadius 0.0
05-22 18:50:36.119  9764  9764 D SurfaceView: 51902665 dispatchDraw mDrawFinished true isAboveParent false (mPrivateFlags & PFLAG_SKIP_DRAW) 0
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal: Playback error
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:   s0.m: Source error
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at s0.I.k(Unknown Source:54)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at s0.I.handleMessage(Unknown Source:325)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at android.os.Handler.dispatchMessage(Handler.java:102)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at android.os.Looper.loopOnce(Looper.java:257)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at android.os.Looper.loop(Looper.java:368)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at android.os.HandlerThread.run(HandlerThread.java:67)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:   Caused by: q0.t: Response code: 418
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at q0.n.f(Unknown Source:497)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at q0.x.f(Unknown Source:9)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at z0.M.b(Unknown Source:25)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at C0.k.run(Unknown Source:34)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
05-22 18:50:39.032  9764 10230 E ExoPlayerImplInternal:       at java.lang.Thread.run(Thread.java:1012)

The log block starts from when I click to play video from its timeline thumbnail and seems to stop logging at the loading icon loop, is this sufficient?

@amigthea commented on GitHub (May 22, 2025): I was able to find a faster workaround, here's the logcat ``` 05-22 18:50:35.831 9764 9764 W WindowOnBackDispatcher: OnBackInvokedCallback is not enabled for the application. 05-22 18:50:35.831 9764 9764 W WindowOnBackDispatcher: Set 'android:enableOnBackInvokedCallback="true"' in the application manifest. 05-22 18:50:35.834 9764 9764 D ViewRootImplExtImpl: wrapConfigInfoIntoFlags rotation=0, smallestScreenWidthDp=411, relayoutAsync=true, newFlags=26935296, title=app.alextran.immich/app.alextran.immich.MainActivity 05-22 18:50:35.834 9764 9764 D VRI[MainActivity]: relayoutWindow result, sizeChanged:false, surfaceControlChanged:false, transformHintChanged:false, mSurfaceSize:Point(1080, 2412), mLastSurfaceSize:Point(1080, 2412), mWidth:1080, mHeight:2412, requestedWidth:1080, requestedHeight:2412, transformHint:0, lastTransformHint:0, installOrientation:0, displayRotation:0, isSurfaceValid:true, attr.flag:-2122252032, useBlast:true, tmpFrames:ClientWindowFrames{frame=[0,0][1080,2412] display=[0,0][1080,2412] parentFrame=[0,0][1080,2412]} 05-22 18:50:35.834 9764 9764 W VRI[MainActivity]: updateBlastSurfaceIfNeeded, surfaceSize:Point(1080, 2412), lastSurfaceSize:Point(1080, 2412), format:-3, blastBufferQueue:android.graphics.BLASTBufferQueue@91f3488 05-22 18:50:35.835 9764 9764 D OplusWindowManager: get WMS extension: android.os.BinderProxy@c1113e6 05-22 18:50:35.841 9764 9764 I SurfaceControl: setExtendedRangeBrightness sc=Surface(name=app.alextran.immich/app.alextran.immich.MainActivity)/@0x44ccfb8,currentBufferRatio=1.0,desiredRatio=1.0 05-22 18:50:35.866 9764 9764 I ExoPlayerImpl: Init 826a140 [AndroidXMedia3/1.5.0] [OnePlus9Pro, LE2123, OnePlus, 34] 05-22 18:50:35.870 9764 9764 W AidlConversionCppNdk: aidl2legacy_AudioChannelLayout_audio_channel_mask_t: no legacy output audio_channel_mask_t found for AudioChannelLayout{layoutMask: 16} 05-22 18:50:35.886 9764 9764 I Quality : Skipped: false 5 cost 44.21756 refreshRate 8287800 bit true processName app.alextran.immich 05-22 18:50:35.887 9764 9764 W VRI[MainActivity]: handleResized abandoned! 05-22 18:50:36.108 9764 9764 D CompatibilityChangeReporter: Compat change id reported: 236825255; UID 10573; state: ENABLED 05-22 18:50:36.108 9764 9764 D BufferQueueConsumer: [](id:262400000002,api:0,p:-1,c:9764) connect: controlledByApp=true 05-22 18:50:36.109 9764 9764 D BufferQueueProducer: [SurfaceView[app.alextran.immich/app.alextran.immich.MainActivity]#1(BLAST Consumer)1](id:262400000001,api:1,p:9764,c:9764) disconnect: api 1 05-22 18:50:36.109 9764 9764 D BufferQueueProducer: [ImageReader-1080x2412f1m3-9764-0](id:262400000002,api:1,p:9764,c:9764) connect: api=1 producerControlledByApp=true 05-22 18:50:36.110 9764 9764 I SurfaceView: 51902665 onAttachedToWindow 05-22 18:50:36.118 9764 9764 D BufferQueueConsumer: [](id:262400000003,api:0,p:-1,c:9764) connect: controlledByApp=false 05-22 18:50:36.118 9764 9764 I SurfaceView: 51902665 visibleChanged -- surfaceCreated 05-22 18:50:36.118 9764 9764 D SurfaceView: 51902665 handleSyncNoBuffer 05-22 18:50:36.119 9764 9764 D SurfaceView: 51902665 finishedDrawing 05-22 18:50:36.119 9764 9764 D SurfaceView: 51902665 performDrawFinished mAttachedToWindow true 05-22 18:50:36.119 9764 9764 D SurfaceView: 51902665 draw mDrawFinished true isAboveParent false (mPrivateFlags & PFLAG_SKIP_DRAW) 0 05-22 18:50:36.119 9764 9764 D SurfaceView: 51902665 clearSurfaceViewPort mCornerRadius 0.0 05-22 18:50:36.119 9764 9764 D SurfaceView: 51902665 dispatchDraw mDrawFinished true isAboveParent false (mPrivateFlags & PFLAG_SKIP_DRAW) 0 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: Playback error 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: s0.m: Source error 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at s0.I.k(Unknown Source:54) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at s0.I.handleMessage(Unknown Source:325) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at android.os.Handler.dispatchMessage(Handler.java:102) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at android.os.Looper.loopOnce(Looper.java:257) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at android.os.Looper.loop(Looper.java:368) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at android.os.HandlerThread.run(HandlerThread.java:67) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: Caused by: q0.t: Response code: 418 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at q0.n.f(Unknown Source:497) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at q0.x.f(Unknown Source:9) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at z0.M.b(Unknown Source:25) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at C0.k.run(Unknown Source:34) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) 05-22 18:50:39.032 9764 10230 E ExoPlayerImplInternal: at java.lang.Thread.run(Thread.java:1012) ``` The log block starts from when I click to play video from its timeline thumbnail and seems to stop logging at the loading icon loop, is this sufficient?
Author
Owner

@rovo89 commented on GitHub (May 23, 2025):

Response code: 418

HTTP status code 418 would mean I'm a teapot?!? Sounds very strange... Is your server on a non-standard port as well?

@amonhk Do you see the same error in your logcat? Do you maybe have a similar phone? From past experience I know that vendors sometimes bring their own implementations of even basic stuff. Although the APIs used (especially HttpsURLConnection.setDefaultSSLSocketFactory()) should be, well, public APIs, which are usually covered by CTS tests, so less freedom for vendors to change the behavior.

You could also try downloading an image that doesn't exist locally, it uses the same Android-native HTTP client, but with less indirections.

@rovo89 commented on GitHub (May 23, 2025): > Response code: 418 HTTP status code 418 would mean [I'm a teapot](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/418)?!? Sounds very strange... Is your server on a non-standard port as well? @amonhk Do you see the same error in your logcat? Do you maybe have a similar phone? From past experience I know that vendors sometimes bring their own implementations of even basic stuff. Although the APIs used (especially `HttpsURLConnection.setDefaultSSLSocketFactory()`) should be, well, public APIs, which are usually covered by CTS tests, so less freedom for vendors to change the behavior. You could also try downloading an image that doesn't exist locally, it uses the same Android-native HTTP client, but with less indirections.
Author
Owner

@amigthea commented on GitHub (May 23, 2025):

HTTP status code 418 would mean I'm a teapot?!? Sounds very strange... Is your server on a non-standard port as well?

you gave me a really good morning laugh, my bad I forgot to specify that my proxy masks the http code 496 to 418
I'm on standard 443
I'm on a oneplus 9 pro

@amigthea commented on GitHub (May 23, 2025): > HTTP status code 418 would mean [I'm a teapot](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/418)?!? Sounds very strange... Is your server on a non-standard port as well? you gave me a really good morning laugh, my bad I forgot to specify that my proxy masks the http code 496 to 418 I'm on standard 443 I'm on a oneplus 9 pro
Author
Owner

@amigthea commented on GitHub (May 23, 2025):

eureka! I solved by logging out from the app, clear its cache and login again on mTLS; now video playback it's flawless, thank for the effort <3

@amigthea commented on GitHub (May 23, 2025): eureka! I solved by logging out from the app, clear its cache and login again on mTLS; now video playback it's flawless, thank for the effort <3
Author
Owner

@amonhk commented on GitHub (May 23, 2025):

I hope I have managed to extract the useful parts of the log.

This is when I open a video that is not present on the device, and the player attempts to play it

5-23 10:20:58.206 20180 20180 I SurfaceControl:  setExtendedRangeBrightness sc=Surface(name=app.alextran.immich/app.alextran.immich.MainActivity)/@0x16ee35,currentBufferRatio=1.0,desiredRatio=1.0
05-23 10:20:58.206  3174  6936 D WindowManager: BarWindow--setVisible: id=1, oldState=2, state=0, mFocuseWindow= Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity} call:
05-23 10:20:58.206  3174  6936 I OplusStatusBarManagerServiceEnhance:  topIsFullscreen fullscreen: false
05-23 10:20:58.207  3174  6936 D WindowManager: BarWindow--setVisible: id=2, oldState=2, state=0, mFocuseWindow= Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity} call:
05-23 10:20:58.210  4242  4328 D RegionSamplingHelper =:    mCompositionSamplingListener.register
05-23 10:20:58.212  4242  4242 D PluginSeedling--Base: SysUiStateHelper-->onFullscreenStateChanged isFullscreen: false
05-23 10:20:58.214  1897  2301 D SurfaceFlinger: addRegionSamplingListener pid: 4242, Area: [0, 3062, 1440, 3216]
05-23 10:20:58.216  4242  4242 D callGcSupression: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])' on a null object reference
05-23 10:20:58.216  4242  4242 W VRI[StatusBar]: handleResized abandoned!
05-23 10:20:58.216  4242  4242 W VRI[NavigationBar_displayId_0]: handleResized abandoned!
05-23 10:20:58.217  4242  4577 W VRI[ScreenDecorOverlay]: handleResized abandoned!
05-23 10:20:58.217  4242  4577 W VRI[ScreenDecorOverlayBottom]: handleResized abandoned!
05-23 10:20:58.220  3174  4908 D WindowManagerServiceExtImpl: extractConfigInfoAndRealFlags rotation=0, screenDp=411, residentWS:0, win=Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity}
05-23 10:20:58.221  3174  4908 V WindowManager: Relayout Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity}: viewVisibility=0, oldvis=0, req=1440x3216, vsysui=LAYOUT_STABLE LAYOUT_HIDE_NAVIGATION LAYOUT_FULLSCREEN, x=0, y=0
05-23 10:20:58.249  1769  1871 W gameoptHal: unknow message 138
05-23 10:20:58.254 20180 20180 D callGcSupression: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])' on a null object reference
05-23 10:20:58.254 20180 20180 W VRI[MainActivity]: handleResized abandoned!
05-23 10:20:58.403  3423  3544 I AIUnit-CrashHandler: monitor pre = y2.b@f312b6d
05-23 10:20:58.503  3174  4908 D OplusWindowManagerServiceEnhance: setInsetAnimationTid: pid 20180 ,tid 20180 ,enable false ,uafEnable true
05-23 10:20:58.599  4362  6171 D TelephonyImpl: getRealNrState start:0
05-23 10:20:58.604  4362  6171 D TelephonyImpl: getRealNrState start:0
05-23 10:20:58.615  4362  6171 D TelephonyImpl: getRealNrState start:0
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal: Playback error
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:   u0.l: Source error
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at u0.G.k(Unknown Source:54)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at u0.G.handleMessage(Unknown Source:325)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at android.os.Handler.dispatchMessage(Handler.java:103)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at android.os.Looper.loopOnce(Looper.java:282)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at android.os.Looper.loop(Looper.java:387)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at android.os.HandlerThread.run(HandlerThread.java:85)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:   Caused by: s0.r: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb400007240c55388: Failure in SSL library, usually a protocol error
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:   error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e3be5f40:0x00000003)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at s0.n.c(Unknown Source:505)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at s0.x.c(Unknown Source:9)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at B0.T.b(Unknown Source:25)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at E0.k.run(Unknown Source:34)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at java.lang.Thread.run(Thread.java:1012)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:   Caused by: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb400007240c55388: Failure in SSL library, usually a protocol error
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:   error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e3be5f40:0x00000003)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:566)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1092)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1076)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:873)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:744)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:709)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:902)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       at s0.n.c(Unknown Source:55)
05-23 10:20:58.694 20180  3352 E ExoPlayerImplInternal:       ... 6 more
05-23 10:20:58.825  4242  4328 I SystemUi--Statusbar: OplusNetworkSpeedControllExImpl-->updateNetworkSpeed: isConnected:true,isSwitchOn:true
05-23 10:20:58.829  4242  4328 I SystemUi--Statusbar: OplusNetworkSpeedControllExImpl-->getTotalByte: totalRxBytes:929997608,totalTxBytes:46456075690,loopbackTxBytes:77610,loopbackRxBytes:70870 ,totalBytes:47385924818
05-23 10:20:58.975  4362  4863 D CommonApp-117-10495: commonScoreStrategy scenarios 5 mdnsScore=0
05-23 10:20:58.975  4362  4863 D CommonApp-117-10495: notifyScoreChange appScene COMMON score 89
05-23 10:20:58.978  4362  4863 D OplusScanQrCodeStatsService: get tencent pkg uid fail android.content.pm.PackageManager$NameNotFoundException: com.tencent.mm
05-23 10:20:59.086  3174  4155 I [UAH_CLIENT]: UahEventAcquire, cmdid:37, pkg:none, identity:OplusUAwareInputHelper
05-23 10:20:59.087  3174  3843 D PowerManagerService: userActivityNoUpdateLocked: groupId=0, eventTime=29490637, event=touch, flags=0x0, uid=1000
05-23 10:20:59.087  3174  4155 D UAH-UahAdaptHelper: adaptSetNotification identity = OplusUAwareInputHelpersrc = 1000 , type 0 ,p1 = -1 ,p2 = -1 ,p3 = -1 ,p4 =
05-23 10:20:59.087  3174  4155 I [UAH_CLIENT]: uahRuleCtl, ruleId:1000, status:0
05-23 10:20:59.089  3174  4015 D OGG_Detector: D:done mCurrStatus: 0
05-23 10:20:59.090 20180 20180 V AutofillManager: requestHideFillUi(null): anchor = null
05-23 10:20:59.165  3174  4155 D UAH-UahAdaptHelper: adaptSetNotification identity = OplusUAwareInputHelpersrc = 1000 , type 1 ,p1 = -1 ,p2 = -1 ,p3 = -1 ,p4 =
05-23 10:20:59.166  3174  4155 I [UAH_CLIENT]: uahRuleCtl, ruleId:1000, status:1
05-23 10:20:59.170 20180 20180 D ViewRootImplExtImpl: the up motion event handled by client, just return
05-23 10:20:59.181 20180 20180 I BackgroundDownloader: Enqueuing task with id d40e0b65-e2ff-472d-adbf-0eeff73f9f0f
05-23 10:20:59.218  3174  4908 D ConnectivityService: requestNetwork for uid/pid:10495/20180 activeRequest: null callbackRequest: 1698 [NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]] callback flags: 0 order: 2147483647 isUidTracked: false declaredMethods: ALL
05-23 10:20:59.221  3174  3877 D OemPaidWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]
05-23 10:20:59.222  3174  3877 D MultiInternetWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]
05-23 10:20:59.222  3174  3877 D WifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]
05-23 10:20:59.222  3174  3877 D UntrustedWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]
05-23 10:20:59.222  3174  3885 D ConnectivityService: NetReassign [1699 : null → 117] [c 0] [a 0] [i 2]
05-23 10:20:59.248 20180 22118 I TaskWorker: Starting task with taskId d40e0b65-e2ff-472d-adbf-0eeff73f9f0f
05-23 10:20:59.261  3174  4908 D NotificationService--NotificationRecord: isSupportRearLight , isRearLight = false mLedRM = false isMultilLed = false
05-23 10:20:59.263  3174  4908 I OplusStatusBarManagerServiceEnhance:  getTopIsFullscreen mTopIsFullscreen: false
05-23 10:20:59.263  3174  4908 W ActivityManager: Permission Denial: getTopApplicationInfo from pid=20180, uid=10495 requires oplus.permission.OPLUS_COMPONENT_SAFE
05-23 10:20:59.263  3174  4908 E OplusNotificationCenter: getForegroundApplication exception
05-23 10:20:59.264  3174  3174 D NotificationService--OplusNotificationManagerServiceExtImpl: isForwardToAssistants: true, case : Is local notification
05-23 10:20:59.264  3174  3174 D NotificationService: This application enqueue notifications is need postDelayed mcsAssistantDelayTime: 200
05-23 10:20:59.482  3174  3219 D NotificationService--OplusNavigationManager: Notification--isSuppressedByDriveMode--userId:-1,mode:false
05-23 10:20:59.483  3174  6936 D NotificationService--OplusNavigationManager: Notification--isSuppressedByDriveMode--userId:-1,mode:false
05-23 10:20:59.489  7669  7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 posted: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null
05-23 10:20:59.491  7695  9418 E music   : Blocked onNotificationPosted StatusBarNotification(pkg=app.alextran.immich user=UserHandle{0} id=-67889516 tag=null key=0|app.alextran.immich|-67889516|null|10495: Notification(channel=background_downloader shortcut=null contentView=null vibrate=null sound=null defaults=0 flags=0 color=0x00000000 actions=1 vis=PRIVATE))
05-23 10:20:59.491  7669  7669 I nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Ignoring notification: app.alextran.immich

This should be the point where I try to download the content.

05-23 10:20:59.489  7669  7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 posted: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null
05-23 10:20:59.491  7695  9418 E music   : Blocked onNotificationPosted StatusBarNotification(pkg=app.alextran.immich user=UserHandle{0} id=-67889516 tag=null key=0|app.alextran.immich|-67889516|null|10495: Notification(channel=background_downloader shortcut=null contentView=null vibrate=null sound=null defaults=0 flags=0 color=0x00000000 actions=1 vis=PRIVATE))
05-23 10:20:59.491  7669  7669 I nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Ignoring notification: app.alextran.immich
05-23 10:20:59.496  4242  4242 E LockScreenNotificationDispatcherImp: updateNotificationListOnKg size:0
05-23 10:20:59.496  4242  4242 I SystemUi--Notification: LockScreenNotificationDispatcherImp-->updateNotificationListOnKg resultCode: 0
05-23 10:20:59.502  4562  4792 I MMKV    : <MMKV_IO.cpp:456::writeActualSize> [oplus_statistics_rom] increase sequence to 3652, crc 2320066956, actualSize 3959
05-23 10:20:59.507  4242  4242 W SystemUi--Common: SubsetBitmapDrawable-->[isValid] innerBitmap?.isRecycled=null
05-23 10:20:59.526  4242  4655 D LocalImageResolver: Couldn't use ImageDecoder for drawable, falling back to non-resized load.
05-23 10:20:59.529  4242  4242 D OplusNotificationDateTimeView: setTime mSettingTimeMillis 1747988459249, mSettingLocalDateTime 2025-05-23T10:20:00.249
05-23 10:20:59.534  4242  4655 D LocalImageResolver: Couldn't use ImageDecoder for drawable, falling back to non-resized load.
05-23 10:20:59.537  4242  4242 D OplusNotificationDateTimeView: setTime mSettingTimeMillis 1747988459249, mSettingLocalDateTime 2025-05-23T10:20:00.249
05-23 10:20:59.550  4242  4242 E LockScreenNotificationDispatcherImp: updateNotificationListOnKg size:0
05-23 10:20:59.550  4242  4242 I SystemUi--Notification: LockScreenNotificationDispatcherImp-->updateNotificationListOnKg resultCode: 0
05-23 10:20:59.553  4242  4242 I SystemUi--Statusbar: OplusNotificationIconAreaController-->updateIconsForLayout: toShow.size = 4, originNotificationCount:4, mLastToShow.size = 3, notificationsNum = 4, hostLayout.visible = 0, hostLayout.width = 280, hostLayout.childCount= 3
05-23 10:20:59.568  4242  4242 I SystemUi--Notification: SeparatePanelAnimation-->updateVisibleChildCount change, currentChildCount: 5 totalCount 6
05-23 10:20:59.634 20180 22118 W TaskWorker: Error for taskId d40e0b65-e2ff-472d-adbf-0eeff73f9f0f: Read error: ssl=0xb4000071e2a66a08: Failure in SSL library, usually a protocol error
05-23 10:20:59.634 20180 22118 W TaskWorker: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e2d64300:0x00000003)
05-23 10:20:59.634 20180 22118 W TaskWorker: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb4000071e2a66a08: Failure in SSL library, usually a protocol error
05-23 10:20:59.634 20180 22118 W TaskWorker: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e2d64300:0x00000003)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:566)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1092)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1076)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:873)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:744)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:709)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:902)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.okio.Okio$2.read(Okio.java:138)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.bbflight.background_downloader.DownloadTaskWorker.r(Unknown Source:181)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.bbflight.background_downloader.TaskWorker.j(Unknown Source:154)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.bbflight.background_downloader.DownloadTaskWorker.i(Unknown Source:150)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at com.bbflight.background_downloader.TaskWorker.h(Unknown Source:301)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at n2.Q1.j(Unknown Source:471)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at p5.a.p(Unknown Source:8)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at H5.F.run(Unknown Source:106)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at A1.e.run(Unknown Source:605)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at O5.k.run(Unknown Source:2)
05-23 10:20:59.634 20180 22118 W TaskWorker:    at O5.b.run(Unknown Source:92)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: Work [ id=7d3b0de2-acda-43da-8b30-2d3cb6f63c1c, tags={ com.bbflight.background_downloader.DownloadTaskWorker, BackgroundDownloader, taskId=d40e0b65-e2ff-472d-adbf-0eeff73f9f0f, group=group_video } ] was cancelled
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: java.util.concurrent.CancellationException: Task was cancelled.
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at d2.i.d(Unknown Source:32)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at d2.i.get(SourceFile:77)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at A1.e.run(Unknown Source:302)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at A1.e.a(Unknown Source:4)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at A1.e.run(Unknown Source:150)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper:      at java.lang.Thread.run(Thread.java:1012)
05-23 10:20:59.654  3174  6932 W JobScheduler: Job didn't exist in JobStore: #app.alextran.immich/47
05-23 10:20:59.656  3174  3174 D NotificationService--OplusBreathLights:  updateLightsStateLocked
05-23 10:20:59.656  3174  3174 D NotificationService--OplusBreathLights:  scheduleLightsOffTimeoutLocked
05-23 10:20:59.662  7669  7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 removed: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null

It seems that the certificate entered in the app is not being used?
I'm on a Find X5 Pro

@amonhk commented on GitHub (May 23, 2025): I hope I have managed to extract the useful parts of the log. This is when I open a video that is not present on the device, and the player attempts to play it ``` 5-23 10:20:58.206 20180 20180 I SurfaceControl: setExtendedRangeBrightness sc=Surface(name=app.alextran.immich/app.alextran.immich.MainActivity)/@0x16ee35,currentBufferRatio=1.0,desiredRatio=1.0 05-23 10:20:58.206 3174 6936 D WindowManager: BarWindow--setVisible: id=1, oldState=2, state=0, mFocuseWindow= Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity} call: 05-23 10:20:58.206 3174 6936 I OplusStatusBarManagerServiceEnhance: topIsFullscreen fullscreen: false 05-23 10:20:58.207 3174 6936 D WindowManager: BarWindow--setVisible: id=2, oldState=2, state=0, mFocuseWindow= Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity} call: 05-23 10:20:58.210 4242 4328 D RegionSamplingHelper =: mCompositionSamplingListener.register 05-23 10:20:58.212 4242 4242 D PluginSeedling--Base: SysUiStateHelper-->onFullscreenStateChanged isFullscreen: false 05-23 10:20:58.214 1897 2301 D SurfaceFlinger: addRegionSamplingListener pid: 4242, Area: [0, 3062, 1440, 3216] 05-23 10:20:58.216 4242 4242 D callGcSupression: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])' on a null object reference 05-23 10:20:58.216 4242 4242 W VRI[StatusBar]: handleResized abandoned! 05-23 10:20:58.216 4242 4242 W VRI[NavigationBar_displayId_0]: handleResized abandoned! 05-23 10:20:58.217 4242 4577 W VRI[ScreenDecorOverlay]: handleResized abandoned! 05-23 10:20:58.217 4242 4577 W VRI[ScreenDecorOverlayBottom]: handleResized abandoned! 05-23 10:20:58.220 3174 4908 D WindowManagerServiceExtImpl: extractConfigInfoAndRealFlags rotation=0, screenDp=411, residentWS:0, win=Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity} 05-23 10:20:58.221 3174 4908 V WindowManager: Relayout Window{8bdc233 u0 app.alextran.immich/app.alextran.immich.MainActivity}: viewVisibility=0, oldvis=0, req=1440x3216, vsysui=LAYOUT_STABLE LAYOUT_HIDE_NAVIGATION LAYOUT_FULLSCREEN, x=0, y=0 05-23 10:20:58.249 1769 1871 W gameoptHal: unknow message 138 05-23 10:20:58.254 20180 20180 D callGcSupression: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[])' on a null object reference 05-23 10:20:58.254 20180 20180 W VRI[MainActivity]: handleResized abandoned! 05-23 10:20:58.403 3423 3544 I AIUnit-CrashHandler: monitor pre = y2.b@f312b6d 05-23 10:20:58.503 3174 4908 D OplusWindowManagerServiceEnhance: setInsetAnimationTid: pid 20180 ,tid 20180 ,enable false ,uafEnable true 05-23 10:20:58.599 4362 6171 D TelephonyImpl: getRealNrState start:0 05-23 10:20:58.604 4362 6171 D TelephonyImpl: getRealNrState start:0 05-23 10:20:58.615 4362 6171 D TelephonyImpl: getRealNrState start:0 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: Playback error 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: u0.l: Source error 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at u0.G.k(Unknown Source:54) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at u0.G.handleMessage(Unknown Source:325) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at android.os.Handler.dispatchMessage(Handler.java:103) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at android.os.Looper.loopOnce(Looper.java:282) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at android.os.Looper.loop(Looper.java:387) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at android.os.HandlerThread.run(HandlerThread.java:85) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: Caused by: s0.r: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb400007240c55388: Failure in SSL library, usually a protocol error 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e3be5f40:0x00000003) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at s0.n.c(Unknown Source:505) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at s0.x.c(Unknown Source:9) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at B0.T.b(Unknown Source:25) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at E0.k.run(Unknown Source:34) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at java.lang.Thread.run(Thread.java:1012) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: Caused by: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb400007240c55388: Failure in SSL library, usually a protocol error 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e3be5f40:0x00000003) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:566) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1092) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1076) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:873) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:744) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:709) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:902) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.okio.Okio$2.read(Okio.java:138) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: at s0.n.c(Unknown Source:55) 05-23 10:20:58.694 20180 3352 E ExoPlayerImplInternal: ... 6 more 05-23 10:20:58.825 4242 4328 I SystemUi--Statusbar: OplusNetworkSpeedControllExImpl-->updateNetworkSpeed: isConnected:true,isSwitchOn:true 05-23 10:20:58.829 4242 4328 I SystemUi--Statusbar: OplusNetworkSpeedControllExImpl-->getTotalByte: totalRxBytes:929997608,totalTxBytes:46456075690,loopbackTxBytes:77610,loopbackRxBytes:70870 ,totalBytes:47385924818 05-23 10:20:58.975 4362 4863 D CommonApp-117-10495: commonScoreStrategy scenarios 5 mdnsScore=0 05-23 10:20:58.975 4362 4863 D CommonApp-117-10495: notifyScoreChange appScene COMMON score 89 05-23 10:20:58.978 4362 4863 D OplusScanQrCodeStatsService: get tencent pkg uid fail android.content.pm.PackageManager$NameNotFoundException: com.tencent.mm 05-23 10:20:59.086 3174 4155 I [UAH_CLIENT]: UahEventAcquire, cmdid:37, pkg:none, identity:OplusUAwareInputHelper 05-23 10:20:59.087 3174 3843 D PowerManagerService: userActivityNoUpdateLocked: groupId=0, eventTime=29490637, event=touch, flags=0x0, uid=1000 05-23 10:20:59.087 3174 4155 D UAH-UahAdaptHelper: adaptSetNotification identity = OplusUAwareInputHelpersrc = 1000 , type 0 ,p1 = -1 ,p2 = -1 ,p3 = -1 ,p4 = 05-23 10:20:59.087 3174 4155 I [UAH_CLIENT]: uahRuleCtl, ruleId:1000, status:0 05-23 10:20:59.089 3174 4015 D OGG_Detector: D:done mCurrStatus: 0 05-23 10:20:59.090 20180 20180 V AutofillManager: requestHideFillUi(null): anchor = null 05-23 10:20:59.165 3174 4155 D UAH-UahAdaptHelper: adaptSetNotification identity = OplusUAwareInputHelpersrc = 1000 , type 1 ,p1 = -1 ,p2 = -1 ,p3 = -1 ,p4 = 05-23 10:20:59.166 3174 4155 I [UAH_CLIENT]: uahRuleCtl, ruleId:1000, status:1 05-23 10:20:59.170 20180 20180 D ViewRootImplExtImpl: the up motion event handled by client, just return 05-23 10:20:59.181 20180 20180 I BackgroundDownloader: Enqueuing task with id d40e0b65-e2ff-472d-adbf-0eeff73f9f0f 05-23 10:20:59.218 3174 4908 D ConnectivityService: requestNetwork for uid/pid:10495/20180 activeRequest: null callbackRequest: 1698 [NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ]] callback flags: 0 order: 2147483647 isUidTracked: false declaredMethods: ALL 05-23 10:20:59.221 3174 3877 D OemPaidWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ] 05-23 10:20:59.222 3174 3877 D MultiInternetWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ] 05-23 10:20:59.222 3174 3877 D WifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ] 05-23 10:20:59.222 3174 3877 D UntrustedWifiNetworkFactory: got request NetworkRequest [ REQUEST id=1699, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VCN_MANAGED&NOT_BANDWIDTH_CONSTRAINED Uid: 10495 RequestorUid: 10495 RequestorPkg: app.alextran.immich UnderlyingNetworks: Null] ] 05-23 10:20:59.222 3174 3885 D ConnectivityService: NetReassign [1699 : null → 117] [c 0] [a 0] [i 2] 05-23 10:20:59.248 20180 22118 I TaskWorker: Starting task with taskId d40e0b65-e2ff-472d-adbf-0eeff73f9f0f 05-23 10:20:59.261 3174 4908 D NotificationService--NotificationRecord: isSupportRearLight , isRearLight = false mLedRM = false isMultilLed = false 05-23 10:20:59.263 3174 4908 I OplusStatusBarManagerServiceEnhance: getTopIsFullscreen mTopIsFullscreen: false 05-23 10:20:59.263 3174 4908 W ActivityManager: Permission Denial: getTopApplicationInfo from pid=20180, uid=10495 requires oplus.permission.OPLUS_COMPONENT_SAFE 05-23 10:20:59.263 3174 4908 E OplusNotificationCenter: getForegroundApplication exception 05-23 10:20:59.264 3174 3174 D NotificationService--OplusNotificationManagerServiceExtImpl: isForwardToAssistants: true, case : Is local notification 05-23 10:20:59.264 3174 3174 D NotificationService: This application enqueue notifications is need postDelayed mcsAssistantDelayTime: 200 05-23 10:20:59.482 3174 3219 D NotificationService--OplusNavigationManager: Notification--isSuppressedByDriveMode--userId:-1,mode:false 05-23 10:20:59.483 3174 6936 D NotificationService--OplusNavigationManager: Notification--isSuppressedByDriveMode--userId:-1,mode:false 05-23 10:20:59.489 7669 7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 posted: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null 05-23 10:20:59.491 7695 9418 E music : Blocked onNotificationPosted StatusBarNotification(pkg=app.alextran.immich user=UserHandle{0} id=-67889516 tag=null key=0|app.alextran.immich|-67889516|null|10495: Notification(channel=background_downloader shortcut=null contentView=null vibrate=null sound=null defaults=0 flags=0 color=0x00000000 actions=1 vis=PRIVATE)) 05-23 10:20:59.491 7669 7669 I nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Ignoring notification: app.alextran.immich ``` This should be the point where I try to download the content. ``` 05-23 10:20:59.489 7669 7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 posted: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null 05-23 10:20:59.491 7695 9418 E music : Blocked onNotificationPosted StatusBarNotification(pkg=app.alextran.immich user=UserHandle{0} id=-67889516 tag=null key=0|app.alextran.immich|-67889516|null|10495: Notification(channel=background_downloader shortcut=null contentView=null vibrate=null sound=null defaults=0 flags=0 color=0x00000000 actions=1 vis=PRIVATE)) 05-23 10:20:59.491 7669 7669 I nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Ignoring notification: app.alextran.immich 05-23 10:20:59.496 4242 4242 E LockScreenNotificationDispatcherImp: updateNotificationListOnKg size:0 05-23 10:20:59.496 4242 4242 I SystemUi--Notification: LockScreenNotificationDispatcherImp-->updateNotificationListOnKg resultCode: 0 05-23 10:20:59.502 4562 4792 I MMKV : <MMKV_IO.cpp:456::writeActualSize> [oplus_statistics_rom] increase sequence to 3652, crc 2320066956, actualSize 3959 05-23 10:20:59.507 4242 4242 W SystemUi--Common: SubsetBitmapDrawable-->[isValid] innerBitmap?.isRecycled=null 05-23 10:20:59.526 4242 4655 D LocalImageResolver: Couldn't use ImageDecoder for drawable, falling back to non-resized load. 05-23 10:20:59.529 4242 4242 D OplusNotificationDateTimeView: setTime mSettingTimeMillis 1747988459249, mSettingLocalDateTime 2025-05-23T10:20:00.249 05-23 10:20:59.534 4242 4655 D LocalImageResolver: Couldn't use ImageDecoder for drawable, falling back to non-resized load. 05-23 10:20:59.537 4242 4242 D OplusNotificationDateTimeView: setTime mSettingTimeMillis 1747988459249, mSettingLocalDateTime 2025-05-23T10:20:00.249 05-23 10:20:59.550 4242 4242 E LockScreenNotificationDispatcherImp: updateNotificationListOnKg size:0 05-23 10:20:59.550 4242 4242 I SystemUi--Notification: LockScreenNotificationDispatcherImp-->updateNotificationListOnKg resultCode: 0 05-23 10:20:59.553 4242 4242 I SystemUi--Statusbar: OplusNotificationIconAreaController-->updateIconsForLayout: toShow.size = 4, originNotificationCount:4, mLastToShow.size = 3, notificationsNum = 4, hostLayout.visible = 0, hostLayout.width = 280, hostLayout.childCount= 3 05-23 10:20:59.568 4242 4242 I SystemUi--Notification: SeparatePanelAnimation-->updateVisibleChildCount change, currentChildCount: 5 totalCount 6 05-23 10:20:59.634 20180 22118 W TaskWorker: Error for taskId d40e0b65-e2ff-472d-adbf-0eeff73f9f0f: Read error: ssl=0xb4000071e2a66a08: Failure in SSL library, usually a protocol error 05-23 10:20:59.634 20180 22118 W TaskWorker: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e2d64300:0x00000003) 05-23 10:20:59.634 20180 22118 W TaskWorker: javax.net.ssl.SSLProtocolException: Read error: ssl=0xb4000071e2a66a08: Failure in SSL library, usually a protocol error 05-23 10:20:59.634 20180 22118 W TaskWorker: error:1000045c:SSL routines:OPENSSL_internal:TLSV1_ALERT_CERTIFICATE_REQUIRED (external/boringssl/src/ssl/tls_record.cc:572 0xb4000071e2d64300:0x00000003) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:566) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1092) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1076) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:873) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:744) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:709) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:902) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.okio.Okio$2.read(Okio.java:138) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.bbflight.background_downloader.DownloadTaskWorker.r(Unknown Source:181) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.bbflight.background_downloader.TaskWorker.j(Unknown Source:154) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.bbflight.background_downloader.DownloadTaskWorker.i(Unknown Source:150) 05-23 10:20:59.634 20180 22118 W TaskWorker: at com.bbflight.background_downloader.TaskWorker.h(Unknown Source:301) 05-23 10:20:59.634 20180 22118 W TaskWorker: at n2.Q1.j(Unknown Source:471) 05-23 10:20:59.634 20180 22118 W TaskWorker: at p5.a.p(Unknown Source:8) 05-23 10:20:59.634 20180 22118 W TaskWorker: at H5.F.run(Unknown Source:106) 05-23 10:20:59.634 20180 22118 W TaskWorker: at A1.e.run(Unknown Source:605) 05-23 10:20:59.634 20180 22118 W TaskWorker: at O5.k.run(Unknown Source:2) 05-23 10:20:59.634 20180 22118 W TaskWorker: at O5.b.run(Unknown Source:92) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: Work [ id=7d3b0de2-acda-43da-8b30-2d3cb6f63c1c, tags={ com.bbflight.background_downloader.DownloadTaskWorker, BackgroundDownloader, taskId=d40e0b65-e2ff-472d-adbf-0eeff73f9f0f, group=group_video } ] was cancelled 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: java.util.concurrent.CancellationException: Task was cancelled. 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at d2.i.d(Unknown Source:32) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at d2.i.get(SourceFile:77) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at A1.e.run(Unknown Source:302) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at A1.e.a(Unknown Source:4) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at A1.e.run(Unknown Source:150) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) 05-23 10:20:59.651 20180 20215 I WM-WorkerWrapper: at java.lang.Thread.run(Thread.java:1012) 05-23 10:20:59.654 3174 6932 W JobScheduler: Job didn't exist in JobStore: #app.alextran.immich/47 05-23 10:20:59.656 3174 3174 D NotificationService--OplusBreathLights: updateLightsStateLocked 05-23 10:20:59.656 3174 3174 D NotificationService--OplusBreathLights: scheduleLightsOffTimeoutLocked 05-23 10:20:59.662 7669 7669 D nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener: Notification -67889516 removed: packageName=app.alextran.immich, when=1747988459249, priority=-1, category=null ``` It seems that the certificate entered in the app is not being used? I'm on a Find X5 Pro
Author
Owner

@amonhk commented on GitHub (May 23, 2025):

eureka! I solved by logging out from the app, clear its cache and login again on mTLS; now video playback it's flawless, thank for the effort <3

I just tried but without success...
Did you import the certificate both in the app and in the android os? self-signed certificate?

@amonhk commented on GitHub (May 23, 2025): > eureka! I solved by logging out from the app, clear its cache and login again on mTLS; now video playback it's flawless, thank for the effort <3 I just tried but without success... Did you import the certificate both in the app and in the android os? self-signed certificate?
Author
Owner

@amigthea commented on GitHub (May 23, 2025):

I just tried but without success... Did you import the certificate both in the app and in the android os?

before v1.133 I had import the client certificate both on android and in immich, but in the recent login attempt that solved the problem I noticed how I could not import the certificate in Immich due to both Import and Remove button on SSL Client Certificate option greyed out; @rovo89 do the implemented bugfix directly use the android certificate store?

self-signed certificate?

I'm using a mTLS certificate signed by a self-signed CA

@amigthea commented on GitHub (May 23, 2025): > I just tried but without success... Did you import the certificate both in the app and in the android os? before v1.133 I had import the client certificate both on android and in immich, but in the recent login attempt that solved the problem I noticed how I could not import the certificate in Immich due to both `Import` and `Remove` button on `SSL Client Certificate` option greyed out; @rovo89 do the implemented bugfix directly use the android certificate store? > self-signed certificate? I'm using a mTLS certificate signed by a self-signed CA
Author
Owner

@rovo89 commented on GitHub (May 23, 2025):

I noticed how I could not import the certificate in Immich due to both Import and Remove button on SSL Client Certificate option greyed out

I think you can only change these options (and allow self-signed server certificates) before you log in.

do the implemented bugfix directly use the android certificate store?

No, those certificates can't simply be used, apps need to go through a selection and approval workflow. The uploaded certificate is used to create a new SSLContext, which is then set as HttpsURLConnection.setDefaultSSLSocketFactory():
529359de2d/mobile/android/app/src/main/kotlin/app/alextran/immich/HttpSSLOptionsPlugin.kt (L55-L76)
The best explanation I have so far is that some vendors have an alternative HTTPS client which ignores the default SSLSocketFactory. Very strange that it worked for you after a fresh login and clearing the cache, as the setup should be done in the running process only.

@rovo89 commented on GitHub (May 23, 2025): > I noticed how I could not import the certificate in Immich due to both `Import` and `Remove` button on `SSL Client Certificate` option greyed out I think you can only change these options (and allow self-signed server certificates) before you log in. > do the implemented bugfix directly use the android certificate store? No, those certificates can't simply be used, apps need to go through a selection and approval workflow. The uploaded certificate is used to create a new `SSLContext`, which is then set as `HttpsURLConnection.setDefaultSSLSocketFactory()`: https://github.com/immich-app/immich/blob/529359de2d98117368fb6c854c133e18c9d3360a/mobile/android/app/src/main/kotlin/app/alextran/immich/HttpSSLOptionsPlugin.kt#L55-L76 The best explanation I have so far is that some vendors have an alternative HTTPS client which ignores the default SSLSocketFactory. Very strange that it worked for you after a fresh login and clearing the cache, as the setup should be done in the running process only.
Author
Owner

@amigthea commented on GitHub (May 23, 2025):

I think you can only change these options (and allow self-signed server certificates) before you log in.

that's the first thing I tried but was already greyed out at that stage, I will uninstall the app completely and try again asap, to repeat those steps in a clean context and give a more reliable feedback

update, I can confirm it asks for the certificate only on first mTLS login. The step i took are:

  • install the app (I used the playstore)
  • open the app and import the mTLS certificate from file manager
  • close and remove the app from memory because somehow the running instance won't load the certificate
  • reopen the app, insert the url and login (on my side there were some other steps due to an identity provider involved in the login process)
  • video can flawlessly play remote content
@amigthea commented on GitHub (May 23, 2025): > I think you can only change these options (and allow self-signed server certificates) before you log in. that's the first thing I tried but was already greyed out at that stage, I will uninstall the app completely and try again asap, to repeat those steps in a clean context and give a more reliable feedback update, I can confirm it asks for the certificate only on first mTLS login. The step i took are: - install the app (I used the playstore) - open the app and import the mTLS certificate from file manager - close and remove the app from memory because somehow the running instance won't load the certificate - reopen the app, insert the url and login (on my side there were some other steps due to an identity provider involved in the login process) - video can flawlessly play remote content
Author
Owner

@amonhk commented on GitHub (May 24, 2025):

Great! I managed to solve the issue—the problem was with the certificate.

This post was very helpful:
https://android.stackexchange.com/questions/252812/how-to-get-chrome-or-any-browser-to-present-a-tls-client-certificate

If the certificate chain is missing intermediate certificates, the issue only affects remote content, while both the app and the browser (Firefox) didn't show any problems.

For now, I've installed the client certificate both in the operating system and in the app, and everything is working fine.

Thank you @rovo89 for this implementation; I've been looking forward to it for a long time!

@amonhk commented on GitHub (May 24, 2025): Great! I managed to solve the issue—the problem was with the certificate. This post was very helpful: https://android.stackexchange.com/questions/252812/how-to-get-chrome-or-any-browser-to-present-a-tls-client-certificate If the certificate chain is missing intermediate certificates, the issue only affects remote content, while both the app and the browser (Firefox) didn't show any problems. For now, I've installed the client certificate both in the operating system and in the app, and everything is working fine. Thank you @rovo89 for this implementation; I've been looking forward to it for a long time!
Author
Owner

@marsara9 commented on GitHub (May 27, 2025):

I think you can only change these options (and allow self-signed server certificates) before you log in.

that's the first thing I tried but was already greyed out at that stage, I will uninstall the app completely and try again asap, to repeat those steps in a clean context and give a more reliable feedback

update, I can confirm it asks for the certificate only on first mTLS login. The step i took are:

  • install the app (I used the playstore)
  • open the app and import the mTLS certificate from file manager
  • close and remove the app from memory because somehow the running instance won't load the certificate
  • reopen the app, insert the url and login (on my side there were some other steps due to an identity provider involved in the login process)
  • video can flawlessly play remote content

I tried a similar set of steps but video playback only works once.

  • app is already installed and I'm logged in
  • sign out so that I can upload the certificate
  • the button is still grayed out, so I kill the app and reopen it.
  • now I can upload a client certificate
  • login per normal
  • I can now play remote videos just fine
  • kill the app as I'm done using it for this session
  • launch it again and try and play a remote video
  • playback is stuck at 0 with an infinite loader.

If I then redo the steps above from the beginning everything works. So while I can get playback to work it requires that I physically sign out after each session. As it doesn't look like it's saving the certificate on each app launch.

@marsara9 commented on GitHub (May 27, 2025): > > I think you can only change these options (and allow self-signed server certificates) before you log in. > > that's the first thing I tried but was already greyed out at that stage, I will uninstall the app completely and try again asap, to repeat those steps in a clean context and give a more reliable feedback > > update, I can confirm it asks for the certificate only on first mTLS login. The step i took are: > - install the app (I used the playstore) > - open the app and import the mTLS certificate from file manager > - close and remove the app from memory because somehow the running instance won't load the certificate > - reopen the app, insert the url and login (on my side there were some other steps due to an identity provider involved in the login process) > - video can flawlessly play remote content I tried a similar set of steps but video playback only works once. - app is already installed and I'm logged in - sign out so that I can upload the certificate - the button is still grayed out, so I kill the app and reopen it. - now I can upload a client certificate - login per normal - I can now play remote videos just fine - kill the app as I'm done using it for this session - launch it again and try and play a remote video - playback is stuck at 0 with an infinite loader. If I then redo the steps above from the beginning everything works. So while I can get playback to work it requires that I physically sign out after each session. As it doesn't look like it's saving the certificate on each app launch.
Author
Owner

@amigthea commented on GitHub (May 27, 2025):

I'm sorry to hear that, it's not something I'm currently facing; tried both killing it (once logged) e rebooting my phone but remote playback still works, certificate is correctly loaded and I even successfully shared one videos on another app

@amigthea commented on GitHub (May 27, 2025): I'm sorry to hear that, it's not something I'm currently facing; tried both killing it (once logged) e rebooting my phone but remote playback still works, certificate is correctly loaded and I even successfully shared one videos on another app
Author
Owner

@wiedaar commented on GitHub (Jun 10, 2025):

video's are playing, the only thing that doesn't work is downloading, i'm using caddy for windows. The workaround we are using is login in via the webbrowser on android, only if you want to download something.

@wiedaar commented on GitHub (Jun 10, 2025): video's are playing, the only thing that doesn't work is downloading, i'm using caddy for windows. The workaround we are using is login in via the webbrowser on android, only if you want to download something.
Author
Owner

@jamesonuk commented on GitHub (Jun 11, 2025):

No, those certificates can't simply be used, apps need to go through a selection and approval workflow. The uploaded certificate is used to create a new SSLContext, which is then set as HttpsURLConnection.setDefaultSSLSocketFactory():

I did raise https://github.com/immich-app/immich/issues/19115 which has been closed to this which my search skills failed to locate.

I do sysadmin work and SSLContext is a way of specifying trust and identity. Java I can do but Kotlin always confuses me.... That code
c03e72c1da/mobile/android/app/src/main/kotlin/app/alextran/immich/HttpSSLOptionsPlugin.kt (L55-L58)

looks like it essentially uses null as the TrustManager this has caused me issues previously and I have ended up loading a CA from creating the TrustManager from that which works fine.

I don't know Android but I know that Home Assistant recognises my local CA (which I have added to Android) and they load TrustManager array slightly differently
a51b398953/common/src/main/kotlin/io/homeassistant/companion/android/common/data/TLSHelper.kt (L24-L26)

Not sure whether this is down to the use of Dart here or something else but HA certainly seems to be able to make use of system certs (is that a case of they have been certified by Google to do so?). Enterprise Java I can dabble in, Android and Kotlin might as well be Klingon... Just throwing out some thoughts

@jamesonuk commented on GitHub (Jun 11, 2025): > No, those certificates can't simply be used, apps need to go through a selection and approval workflow. The uploaded certificate is used to create a new `SSLContext`, which is then set as `HttpsURLConnection.setDefaultSSLSocketFactory()`: I did raise https://github.com/immich-app/immich/issues/19115 which has been closed to this which my search skills failed to locate. I do sysadmin work and `SSLContext` is a way of specifying trust and identity. Java I can do but Kotlin always confuses me.... That code https://github.com/immich-app/immich/blob/c03e72c1da810979e820e7922996f969941dc852/mobile/android/app/src/main/kotlin/app/alextran/immich/HttpSSLOptionsPlugin.kt#L55-L58 looks like it essentially uses null as the `TrustManager` this has caused me issues previously and I have ended up loading a CA from creating the `TrustManager` from that which works fine. I don't know Android but I know that Home Assistant recognises my local CA (which I have added to Android) and they load `TrustManager` array slightly differently https://github.com/home-assistant/android/blob/a51b3989531bbf16ce77d451f3835b65ef7931dd/common/src/main/kotlin/io/homeassistant/companion/android/common/data/TLSHelper.kt#L24-L26 Not sure whether this is down to the use of Dart here or something else but HA certainly seems to be able to make use of system certs (is that a case of they have been certified by Google to do so?). Enterprise Java I can dabble in, Android and Kotlin might as well be Klingon... Just throwing out some thoughts
Author
Owner

@wiedaar commented on GitHub (Jun 11, 2025):

i have no idea what happened but today we can download again, i've changed nothing.

@wiedaar commented on GitHub (Jun 11, 2025): i have no idea what happened but today we can download again, i've changed nothing.
Author
Owner

@rovo89 commented on GitHub (Jun 12, 2025):

Not sure whether this is down to the use of Dart here or something else

Yes. Because the code you linked to is only used in a few scenarios like playing videos which aren't available locally or downloading media. The majority of network communication is done via Dart, and they use their own HTTP/SSL implementation based on OpenSSL directly. I did some investigations in https://github.com/immich-app/immich/issues/15230#issuecomment-2584057446, but it goes through a lot of layers... My understanding is that the Dart implementation is so generic that it doesn't use the Android (or iOS) trust store, and therefore doesn't know about certs you added to it. For mTLS it's even more tricky because the certificate can't be retrieved from the secure store, instead the Android code uses an opaque object for the key manager, and when it comes to signing some bytes, that is handed over to the system.

@rovo89 commented on GitHub (Jun 12, 2025): > Not sure whether this is down to the use of Dart here or something else Yes. Because the code you linked to is only used in a few scenarios like playing videos which aren't available locally or downloading media. The majority of network communication is done via Dart, and they use their own HTTP/SSL implementation based on OpenSSL directly. I did some investigations in https://github.com/immich-app/immich/issues/15230#issuecomment-2584057446, but it goes through a lot of layers... My understanding is that the Dart implementation is so generic that it doesn't use the Android (or iOS) trust store, and therefore doesn't know about certs you added to it. For mTLS it's even more tricky because the certificate can't be retrieved from the secure store, instead the Android code uses an opaque object for the key manager, and when it comes to signing some bytes, that is handed over to the system.
Author
Owner

@Tupsi commented on GitHub (Sep 2, 2025):

I am confused. My ticket #21520 got closed saying it is a duplicate of this. But this here states contradicting information.

First it says, there is an experimental feature (which I can not find anywhere in the app)

Manually imported root CA / SSL certificate

then it says under "Solution" that it is no longer needed because of the new feature from #14437. My issue is exactly describing that this does not work with installed root CAs.

There is also a 2nd slightly misleading part in the article, when you combine the proxy headers with a solution of "use a vpn". Just want to point out, that I just instaled pangolin as vpn service and was very glad to see, that you have an option to enter exactly such proxy headers, so I get around the auth process in pangolin. So please do not remove that!

@Tupsi commented on GitHub (Sep 2, 2025): I am confused. My ticket #21520 got closed saying it is a duplicate of this. But this here states contradicting information. First it says, there is an experimental feature (which I can not find anywhere in the app) Manually imported root CA / SSL certificate then it says under "Solution" that it is no longer needed because of the new feature from #14437. My issue is exactly describing that this does not work with installed root CAs. There is also a 2nd slightly misleading part in the article, when you combine the proxy headers with a solution of "use a vpn". Just want to point out, that I just instaled pangolin as vpn service and was very glad to see, that you have an option to enter exactly such proxy headers, so I get around the auth process in pangolin. So please do not remove that!
Author
Owner

@Fmstrat commented on GitHub (Sep 2, 2025):

@Tupsi skim the issue looking for my username and you'll see why. The dual URL feature has no impact on CA.

It seems Immich uses a special SSL stack to support (IMO a really weird) cert-per-device use case, ignoring the system CA. I'm going to guess a primary Dev uses this setup, which is why it hasn't been shifted to just standard libraries that use the standard custom CA method.

Comments from devs above make ot seem like they think it's an issue with Dart libraries, but every Dart application I've contributed to has zero issues with custom CAs, so I'm unclear on if there will ever be a solution.

@Fmstrat commented on GitHub (Sep 2, 2025): @Tupsi skim the issue looking for my username and you'll see why. The dual URL feature has no impact on CA. It seems Immich uses a special SSL stack to support (IMO a really weird) cert-per-device use case, ignoring the system CA. I'm going to guess a primary Dev uses this setup, which is why it hasn't been shifted to just standard libraries that use the standard custom CA method. Comments from devs above make ot seem like they think it's an issue with Dart libraries, but every Dart application I've contributed to has zero issues with custom CAs, so I'm unclear on if there will ever be a solution.
Author
Owner

@rovo89 commented on GitHub (Sep 2, 2025):

It seems Immich uses a special SSL stack to support (IMO a really weird) cert-per-device use case, ignoring the system CA.

With the cert-per-device, is it possible that you mean client certificates aka. mTLS? If yes, that's a rather niche use-case of an additional authentication layer, but it's completely independent from the more common use-case of a self-signed server certificate. I helped adding fixes/workarounds for the former, but there were already some in place for the latter.

I had looked through the source code of Dart back then and indeed found signs that it doesn't use Android's certificate store but brings its own set of trusted certificates, which obviously doesn't include the ones uploaded by the user. There were lots of GitHub issues around this, but I don't think any solution.

Is there any small sample project which uses Dart's standard HttpClient and is able to connect to an HTTPS server with a self-signed certificate that has been uploaded to Android?

@rovo89 commented on GitHub (Sep 2, 2025): > It seems Immich uses a special SSL stack to support (IMO a really weird) cert-per-device use case, ignoring the system CA. With the cert-per-device, is it possible that you mean *client* certificates aka. mTLS? If yes, that's a rather niche use-case of an additional authentication layer, but it's completely independent from the more common use-case of a self-signed *server* certificate. I helped adding fixes/workarounds for the former, but there were already some in place for the latter. I had looked through the source code of Dart back then and indeed found signs that it doesn't use Android's certificate store but brings its own set of trusted certificates, which obviously doesn't include the ones uploaded by the user. There were lots of GitHub issues around this, but I don't think any solution. Is there any small sample project which uses Dart's standard HttpClient and is able to connect to an HTTPS server with a self-signed certificate that has been uploaded to Android?
Author
Owner

@Tupsi commented on GitHub (Sep 2, 2025):

@rovo89 you should stop using the term "self signed" as my case is not self signed. Having your own root CA as trust anchor is a total valid use case in a scenario where you either do not trust the other parties or do not have the ability to connect to said parties over your network.

@Fmstrat
thanks for your explanation, helps me giving up on the whole subject to find a solution (as there simply is none as long as the app uses its own set of trusted CAs). I will just use only use my external connection url from no one for everything. With my internet connection I can live with the speed impact which is included in the decision.

My initial idea of writing the bug report was just that; reporting something that is outside of the expected way the app acts.
If I go out of my way to create my own root CA, create certificates which are in a chain with that and then upload/install the public part of that root CA to my device my expectation is that any warnings/error go away, because this is now a valid certificate in the eyes of the OS.

I understand now, that this is in the "will not fix" category of the developer, which I can accept.

@Tupsi commented on GitHub (Sep 2, 2025): @rovo89 you should stop using the term "self signed" as my case is not self signed. Having your own root CA as trust anchor is a total valid use case in a scenario where you either do not trust the other parties or do not have the ability to connect to said parties over your network. @Fmstrat thanks for your explanation, helps me giving up on the whole subject to find a solution (as there simply is none as long as the app uses its own set of trusted CAs). I will just use only use my external connection url from no one for everything. With my internet connection I can live with the speed impact which is included in the decision. My initial idea of writing the bug report was just that; reporting something that is outside of the expected way the app acts. If I go out of my way to create my own root CA, create certificates which are in a chain with that and then upload/install the public part of that root CA to my device my expectation is that any warnings/error go away, because this is now a valid certificate in the eyes of the OS. I understand now, that this is in the "will not fix" category of the developer, which I can accept.
Author
Owner

@rovo89 commented on GitHub (Sep 2, 2025):

you should stop using the term "self signed" as my case is not self signed

You're right, that was a bit of a simplification and mainly meant to differentiate from mTLS. Self-signed certs are probably what most people manage to set up though, if they want to setup HTTPS by themselves, without Let's Encrypt etc. In the end, the handling in the app isn't different, you need to get the CA into the trust store.

my device my expectation is that any warnings/error go away, because this is now a valid certificate in the eyes of the OS

Yep, I would also expect that, and would have loved to take an easy solution, but as far as I can see, the app does use the standard framework that Flutter/Dart offers, which I believe doesn't handle this case (in contrast to native Android apps). So the sentiment of "will not fix" (as in "they don't want to") isn't right, at least I couldn't think of a way to make it work. If somebody wants to prove otherwise with a minimal app, I'm pretty sure it would be considered. Until then, the toggle to disable certificate checks might make the errors go away, but is the opposite of what you want to achive.

@rovo89 commented on GitHub (Sep 2, 2025): > you should stop using the term "self signed" as my case is not self signed You're right, that was a bit of a simplification and mainly meant to differentiate from mTLS. Self-signed certs are probably what most people manage to set up though, if they want to setup HTTPS by themselves, without Let's Encrypt etc. In the end, the handling in the app isn't different, you need to get the CA into the trust store. > my device my expectation is that any warnings/error go away, because this is now a valid certificate in the eyes of the OS Yep, I would also expect that, and would have loved to take an easy solution, but as far as I can see, the app *does use* the standard framework that Flutter/Dart offers, which I believe doesn't handle this case (in contrast to native Android apps). So the sentiment of "will not fix" (as in "they don't want to") isn't right, at least I couldn't think of a way to make it work. If somebody wants to prove otherwise with a minimal app, I'm pretty sure it would be considered. Until then, the toggle to disable certificate checks might make the errors go away, but is the opposite of what you want to achive.
Author
Owner

@Tupsi commented on GitHub (Sep 2, 2025):

aye, sorry if that came across (with the wont fix) tag. It wasnt ment as a slight, just a fact as I understood it. If, for whatever reason, the framework at some point in the future decides to use system certs instead of its on set it will just start to work. That I understood. As long, as the dev here uses the framweork he is bound by what the framework dictates. In the end I got my answer and can configure the app so that it works and that is all that counts for me in the end, so all good.

I am glad the whole immich project exists and does what it does, so thank you for this!

@Tupsi commented on GitHub (Sep 2, 2025): aye, sorry if that came across (with the wont fix) tag. It wasnt ment as a slight, just a fact as I understood it. If, for whatever reason, the framework at some point in the future decides to use system certs instead of its on set it will just start to work. That I understood. As long, as the dev here uses the framweork he is bound by what the framework dictates. In the end I got my answer and can configure the app so that it works and that is all that counts for me in the end, so all good. I am glad the whole immich project exists and does what it does, so thank you for this!
Author
Owner

@Fmstrat commented on GitHub (Sep 2, 2025):

@rovo89 Oh interesting, I had thought the library used that supported mTLS (yes, I'm brain dead and forgot the name) was the thing conflicting with custom CA support.

Is there any small sample project which uses Dart's standard HttpClient and is able to connect to an HTTPS server with a self-signed certificate that has been uploaded to Android?

Seems progress has been made as of July this year. Here's a PR of adding support for user supplied CAs (via the Android Certificate Store) to another Flutter app: https://github.com/jmshrv/finamp/pull/1276/files

This was based off the Dart SDK conversations in: https://github.com/dart-lang/sdk/issues/50435

@Fmstrat commented on GitHub (Sep 2, 2025): @rovo89 Oh interesting, I had thought the library used that supported mTLS (yes, I'm brain dead and forgot the name) was the thing conflicting with custom CA support. > Is there any small sample project which uses Dart's standard HttpClient and is able to connect to an HTTPS server with a self-signed certificate that has been uploaded to Android? Seems progress has been made as of July this year. Here's a PR of adding support for user supplied CAs (via the Android Certificate Store) to another Flutter app: https://github.com/jmshrv/finamp/pull/1276/files This was based off the Dart SDK conversations in: https://github.com/dart-lang/sdk/issues/50435
Author
Owner

@rovo89 commented on GitHub (Sep 2, 2025):

This sounds promising: https://github.com/immich-app/immich/pull/19830#issuecomment-3243963077
IIUC, they're planning to replace the network stack with Cronet on Android, which is based on Chromium, so might use the system trust store out if the box. Not sure about e.g. video player which already uses the native client, and about mTLS in general (needs a few steps to choose and allow a certificate), let's see.

@rovo89 commented on GitHub (Sep 2, 2025): This sounds promising: https://github.com/immich-app/immich/pull/19830#issuecomment-3243963077 IIUC, they're planning to replace the network stack with Cronet on Android, which is based on Chromium, so might use the system trust store out if the box. Not sure about e.g. video player which already uses the native client, and about mTLS in general (needs a few steps to choose and allow a certificate), let's see.
Author
Owner

@polomani commented on GitHub (Sep 10, 2025):

@rovo89 does the new beta timeline work for you with the mTLS enabled?
I am getting 403s (mTLS failure) and basically cannot backup using beta timeline: https://github.com/immich-app/immich/issues/21006#issuecomment-3275734409

@polomani commented on GitHub (Sep 10, 2025): @rovo89 does the new beta timeline work for you with the mTLS enabled? I am getting 403s (mTLS failure) and basically cannot backup using beta timeline: https://github.com/immich-app/immich/issues/21006#issuecomment-3275734409
Author
Owner

@amigthea commented on GitHub (Sep 10, 2025):

beta timeline over mTLS is working for me

@amigthea commented on GitHub (Sep 10, 2025): beta timeline over mTLS is working for me
Author
Owner

@rovo89 commented on GitHub (Sep 10, 2025):

@rovo89 does the new beta timeline work for you with the mTLS enabled?
I am getting 403s (mTLS failure) and basically cannot backup using beta timeline: https://github.com/immich-app/immich/issues/21006#issuecomment-3275734409

Yes, if works for me. Not too sure about the background upload (might need some toggling off and on), but at least when I open the app, it syncs without requiring any actions from my side.

@rovo89 commented on GitHub (Sep 10, 2025): > @rovo89 does the new beta timeline work for you with the mTLS enabled? > I am getting 403s (mTLS failure) and basically cannot backup using beta timeline: https://github.com/immich-app/immich/issues/21006#issuecomment-3275734409 Yes, if works for me. Not too sure about the background upload (might need some toggling off and on), but at least when I open the app, it syncs without requiring any actions from my side.
Author
Owner

@willm commented on GitHub (Sep 13, 2025):

Mtls with the beta timeline is not working for me. I'm curious though about the original message saying these features are not needed because of others. I use mtls to expose immich publicly with trusted users. It's a one time setup to install the certificate on my family's devices. A lot of people suggest some kind of VPN solution, but that's another app to install, enable, have an account on and host. What's the official recommended way to securely expose immich over the internet with minimal fuss?

@willm commented on GitHub (Sep 13, 2025): Mtls with the beta timeline is not working for me. I'm curious though about the original message saying these features are not needed because of others. I use mtls to expose immich publicly with trusted users. It's a one time setup to install the certificate on my family's devices. A lot of people suggest some kind of VPN solution, but that's another app to install, enable, have an account on and host. What's the official recommended way to securely expose immich over the internet with minimal fuss?
Author
Owner

@mmomjian commented on GitHub (Sep 13, 2025):

The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate.

@mmomjian commented on GitHub (Sep 13, 2025): The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate.
Author
Owner

@jamesonuk commented on GitHub (Sep 13, 2025):

The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate.

There is no such thing as a real SSL certificate and you are mixing up server and client certificates. I presume you are talking about certificates issued by one on the main CAs (the ones that browsers trust by default)?

Server certificates are for clients to have some confidence the server is who it claims to be (through trust).

Client certificates are for the server to check the client is who they say they are.

It is quite possible to have a reverse proxy with a "real SSL" certificate but use a completely different CA for client certificates.

Full two way TLS (mTLS) is about the client trusting the server and the server trusting the client which is what I believe most people are talking about here.

@jamesonuk commented on GitHub (Sep 13, 2025): > The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate. There is no such thing as a real SSL certificate and you are mixing up server and client certificates. I presume you are talking about certificates issued by one on the main CAs (the ones that browsers trust by default)? Server certificates are for clients to have some confidence the server is who it claims to be (through trust). Client certificates are for the server to check the client is who they say they are. It is quite possible to have a reverse proxy with a "real SSL" certificate but use a completely different CA for client certificates. Full two way TLS (mTLS) is about the client trusting the server and the server trusting the client which is what I believe most people are talking about here.
Author
Owner

@mmomjian commented on GitHub (Sep 13, 2025):

Yes; I mean a server SSL certificate that is issued by a default/trusted global CA. We don’t officially recommend client certificates in any capacity.

@mmomjian commented on GitHub (Sep 13, 2025): Yes; I mean a server SSL certificate that is issued by a default/trusted global CA. We don’t officially recommend client certificates in any capacity.
Author
Owner

@nordewal commented on GitHub (Sep 13, 2025):

The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate.

Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server. Client certificates ("mTLS") are usually used in addition with "a reverse proxy with a real SSL certificate."

People (or at least me) are using mTLS to protect the immich setup/server from bad actors.

Immich is still rather young and likely has many (un-)known vulnerabilities - even though the team is doing is really great job. Running it behind a reverse proxy that requires a client certificate ("mTLS") ensures that only trusted clients can reach the immich server/API.

This is similar to a VPN, however much more convenient. Why? Because I just need to add the client certificate on the devices that should be able to reach my immich instance (my phone & my wife's phone). This removes the need for any other app/VPN client to be installed and running.

@nordewal commented on GitHub (Sep 13, 2025): > The officially recommended ways would be a VPN or a reverse proxy with a real SSL certificate. Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server. Client certificates ("mTLS") are usually used in addition with "a reverse proxy with a real SSL certificate." People (or at least me) are using mTLS to protect the immich setup/server from bad actors. Immich is still rather young and likely has many (un-)known vulnerabilities - even though the team is doing is really great job. Running it behind a reverse proxy that requires a client certificate ("mTLS") ensures that only trusted clients can reach the immich server/API. This is similar to a VPN, however much more convenient. Why? Because I just need to add the client certificate on the devices that should be able to reach my immich instance (my phone & my wife's phone). This removes the need for any other app/VPN client to be installed and running.
Author
Owner

@rovo89 commented on GitHub (Sep 13, 2025):

For exactly these reasons, I use mTLS for my services. It is working for me though with Immich, including the beta timeline, so it would be worth investigating what the problem is for you. You surely have uploaded the certificate in the app and it worked with the old timeline?

@rovo89 commented on GitHub (Sep 13, 2025): For exactly these reasons, I use mTLS for my services. It is working for me though with Immich, including the beta timeline, so it would be worth investigating what the problem is for you. You surely have uploaded the certificate in the app and it worked with the old timeline?
Author
Owner

@willm commented on GitHub (Sep 13, 2025):

My tiny server that I run as an enthusiast in my spare time doesn't have the massive team that a mainstream website or photo serving service has. I already have a reverse proxy with a real let's encrypt certificate, rate limiting and fail2ban on it. A VPN makes things unusable for my family so client certificates offer a way to guarantee that only they can access the site.
Maybe TLS + http basic auth with a long password would do the trick, is that officially supported?

@willm commented on GitHub (Sep 13, 2025): My tiny server that I run as an enthusiast in my spare time doesn't have the massive team that a mainstream website or photo serving service has. I already have a reverse proxy with a real let's encrypt certificate, rate limiting and fail2ban on it. A VPN makes things unusable for my family so client certificates offer a way to guarantee that only they can access the site. Maybe TLS + http basic auth with a long password would do the trick, is that officially supported?
Author
Owner

@bo0tzz commented on GitHub (Sep 13, 2025):

Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server.

Not quite. Whether you're using a reverse proxy, a cert from a browser-trusted CA, or whether the Immich instance is publically accessible are all entirely unrelated to eachother.

@bo0tzz commented on GitHub (Sep 13, 2025): > Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server. Not quite. Whether you're using a reverse proxy, a cert from a browser-trusted CA, or whether the Immich instance is publically accessible are all entirely unrelated to eachother.
Author
Owner

@EssGeeEich commented on GitHub (Sep 13, 2025):

Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server.

Not quite. Whether you're using a reverse proxy, a cert from a browser-trusted CA, or whether the Immich instance is publically accessible are all entirely unrelated to eachother.

Still, you're missing the point, like many others in this thread (including some that I think are active developers in this project).

Using a custom CA's certificate or a public CA's certificate does not block an attacker from bruteforcing a login or exploit known (or eventually unknown) security holes, since it's just a way of saying that that server is whoever it says it is.

mTLS, on the other hand, does, since it requires that the CLIENT has a certificate that the server approves - and is a way to forcefully verify that a CLIENT is whoever says it is, before the server even interprets its request -- ESPECIALLY if the mTLS check happens in the reverse proxy, like all of the mTLS users are doing right now.

Since this works before the Immich daemon receives the request, any security issue on Immich is mitigated by this check, since an attacker would need a fully verified client key in order to even attempt to attack the Immich server.

@EssGeeEich commented on GitHub (Sep 13, 2025): > > Running immich behing "a reverse proxy with a real SSL certificate" allows anyone to connect to the immich server. > > Not quite. Whether you're using a reverse proxy, a cert from a browser-trusted CA, or whether the Immich instance is publically accessible are all entirely unrelated to eachother. Still, you're missing the point, like many others in this thread (including some that I think are active developers in this project). Using a custom CA's certificate or a public CA's certificate does not block an attacker from bruteforcing a login or exploit known (or eventually unknown) security holes, since it's just a way of saying that that server is whoever it says it is. mTLS, on the other hand, does, since it requires that the CLIENT has a certificate that the server approves - and is a way to forcefully verify that a CLIENT is whoever says it is, before the server even interprets its request -- ESPECIALLY if the mTLS check happens in the reverse proxy, like all of the mTLS users are doing right now. Since this works before the Immich daemon receives the request, any security issue on Immich is mitigated by this check, since an attacker would need a fully verified client key in order to even attempt to attack the Immich server.
Author
Owner

@mmomjian commented on GitHub (Sep 13, 2025):

If complete protection with a private key (a la mTLS) is what you are after, we would recommend a VPN. No one is suggesting a reverse proxy with a globally-trusted TLS cert provides verification of the client identity. OIDC/OAuth may be a workable middle ground here if you are comfortable with setting that up.

@mmomjian commented on GitHub (Sep 13, 2025): If complete protection with a private key (a la mTLS) is what you are after, we would recommend a VPN. No one is suggesting a reverse proxy with a globally-trusted TLS cert provides verification of the client identity. OIDC/OAuth may be a workable middle ground here if you are comfortable with setting that up.
Author
Owner

@amigthea commented on GitHub (Sep 13, 2025):

I agree with people saying that VPN would be an hassle to mantain in comparison. My setup has both mTLS and OIDC identity check, that should be considered as complementary and not an alternative

@amigthea commented on GitHub (Sep 13, 2025): I agree with people saying that VPN would be an hassle to mantain in comparison. My setup has both mTLS and OIDC identity check, that should be considered as complementary and not an alternative
Author
Owner

@polomani commented on GitHub (Sep 13, 2025):

I think we need to debug the issue with the mTLS + beta timeline backup myself and @willm is getting.

I poked a bit, and since the timeline worked for @rovo89 @amigthea I figured it was something with my setup, so I found the culprit. The error Failed to set SSL options: Null cannot be cast to non-null type kotlin.String was caused due to the cert not having the password. dad81af6e3/mobile/lib/utils/http_ssl_options.dart (L38)
Once I've created a certificate with a passkey, the timeline backup started to work on Android.

However, that didn't solve the issue on IOS, so I suspect it's something different there.
I use haproxy, so I see in the logs that the certificate was not passed in

[13/Sep/2025:18:36:45.340] https-in~ https-in/<NOSRV> 403 192 POST https://myserver.com/api/assets ssl_s_dn="" ssl_i_dn="" hascrt=0 verify=0     

Example of the log with correct client cert:

 [13/Sep/2025:19:00:13.203] https-in~ backend_servers/immich-server 200 258 POST /api/auth/validateToken ssl_s_dn="/O=Immich/CN=ImmichClient" ssl_i_dn="/CN=MyRootCA" hascrt=1 verify=0 

So by filtering the /api/assets endpoint from mTLS check I managed to work around the issue both on Android and IOS for me, but it's of course not ideal.

acl public_assets_endpoint path_reg ^/api/assets$
acl client_has_cert ssl_c_used

# Require mTLS for everything EXCEPT assets endpoint
http-request deny if !client_has_cert !public_assets_endpoint

Maybe any devs here know what is different when /api/assets endpoint is being called from the app?

@polomani commented on GitHub (Sep 13, 2025): I think we need to debug the issue with the mTLS + beta timeline backup myself and @willm is getting. I poked a bit, and since the timeline worked for @rovo89 @amigthea I figured it was something with my setup, so I found the culprit. The error `Failed to set SSL options: Null cannot be cast to non-null type kotlin.String` was caused due to the cert not having the password. https://github.com/immich-app/immich/blob/dad81af6e3425fdff23373b698ed552b25028ef8/mobile/lib/utils/http_ssl_options.dart#L38 Once I've created a certificate with a passkey, the timeline backup started to work on Android. However, that didn't solve the issue on IOS, so I suspect it's something different there. I use haproxy, so I see in the logs that the certificate was not passed in ``` [13/Sep/2025:18:36:45.340] https-in~ https-in/<NOSRV> 403 192 POST https://myserver.com/api/assets ssl_s_dn="" ssl_i_dn="" hascrt=0 verify=0 ``` Example of the log with correct client cert: ``` [13/Sep/2025:19:00:13.203] https-in~ backend_servers/immich-server 200 258 POST /api/auth/validateToken ssl_s_dn="/O=Immich/CN=ImmichClient" ssl_i_dn="/CN=MyRootCA" hascrt=1 verify=0 ``` So by filtering the `/api/assets` endpoint from mTLS check I managed to work around the issue both on Android and IOS for me, but it's of course not ideal. ``` acl public_assets_endpoint path_reg ^/api/assets$ acl client_has_cert ssl_c_used # Require mTLS for everything EXCEPT assets endpoint http-request deny if !client_has_cert !public_assets_endpoint ``` Maybe any devs here know what is different when /api/assets endpoint is being called from the app?
Author
Owner

@willm commented on GitHub (Sep 13, 2025):

I'm on android and no my client cert doesn't have a password, that error message is exactly what I'm getting, anyway, I also have users on iOS.

I should also reference this issue where I'm specifically talking about this.

@willm commented on GitHub (Sep 13, 2025): I'm on android and no my client cert doesn't have a password, that error message is exactly what I'm getting, anyway, I also have users on iOS. I should also reference [this issue](https://github.com/immich-app/immich/issues/21006#issuecomment-3288748749) where I'm specifically talking about this.
Author
Owner

@ianhattendorf commented on GitHub (Sep 26, 2025):

Just noticed this issue, also posted in https://github.com/immich-app/immich/issues/22320#issuecomment-3336466719. Backups are failing to upload for me when mTLS is enabled on the beta timeline from the iOS app. I'm using a letsencrypt cert for the server cert and a cert from a trusted private CA for the client cert.

I'm experiencing this issue with server 1.143.1 iOS app 1.143.1.build.226 when mTLS is enabled. Everything else appears to be working on the beta timeline (loading remote images, server stats, etc.).

When clicking the backup icon -> toggling Enable Backup -> clicking View Details I see that no progress is made on the uploads and they turn red.

If I turn off the beta timeline, uploads start working again. If I log out and change to my server URL without mTLS enabled (same Traefik config minus mTLS config), uploads start working again.

@ianhattendorf commented on GitHub (Sep 26, 2025): Just noticed this issue, also posted in https://github.com/immich-app/immich/issues/22320#issuecomment-3336466719. Backups are failing to upload for me when mTLS is enabled on the beta timeline from the iOS app. I'm using a letsencrypt cert for the server cert and a cert from a trusted private CA for the client cert. > I'm experiencing this issue with server 1.143.1 iOS app 1.143.1.build.226 when mTLS is enabled. Everything else appears to be working on the beta timeline (loading remote images, server stats, etc.). > When clicking the backup icon -> toggling Enable Backup -> clicking View Details I see that no progress is made on the uploads and they turn red. > If I turn off the beta timeline, uploads start working again. If I log out and change to my server URL without mTLS enabled (same Traefik config minus mTLS config), uploads start working again.
Author
Owner

@Blurazzle commented on GitHub (Sep 26, 2025):

Hopefully not redundant, and it might even be a known issue. But on my iPhone with iOS 18.7, Immich version 1.43.1 is not able to upload new images when I use a self‑signed certificate on an Nginx reverse proxy. The test was done with the “new timeline” option under Advanced Settings enabled.

Nginx has the advised configuration, and the certificates are made with mkcert. Web and Android do not have any problem with the self‑signed certificate. On my iPhone all functions work with the self‑signed certificate as far as I have been able to test, but uploading new images fails. When I replace the self‑signed certificate with a certificate from Let’s Encrypt, the problem is resolved on the iPhone app.

Reading the discussion here, I think it is important for Immich at some point to define clearly what is supported and what isn’t. I would like to have mTLS working (not yet tested), but if it is not possible to support it consistently because of complexity in Immich’s tech stack, it might be better to remove it.

@Blurazzle commented on GitHub (Sep 26, 2025): Hopefully not redundant, and it might even be a known issue. But on my iPhone with iOS 18.7, Immich version 1.43.1 is not able to upload new images when I use a self‑signed certificate on an Nginx reverse proxy. The test was done with the “new timeline” option under Advanced Settings enabled. Nginx has the [advised configuration](https://docs.immich.app/administration/reverse-proxy), and the certificates are made with [mkcert](https://github.com/FiloSottile/mkcert). Web and Android do not have any problem with the self‑signed certificate. On my iPhone all functions work with the self‑signed certificate as far as I have been able to test, but uploading new images fails. When I replace the self‑signed certificate with a certificate from Let’s Encrypt, the problem is resolved on the iPhone app. Reading the discussion here, I think it is important for Immich at some point to define clearly what is supported and what isn’t. I would like to have mTLS working (not yet tested), but if it is not possible to support it consistently because of complexity in Immich’s tech stack, it might be better to remove it.
Author
Owner

@bunsenhd commented on GitHub (Sep 26, 2025):

@jonkerw85 iOS and self signed is a real pain as they are never trusted by the OS unless you explicitly trust in Certificate Trust Settings... using a wildcard LE cert on your proxy, this is the way :).

On my setup, I use a wildcard LE certificate for the proxy, and a Private CA for MTLS. My problem is with the new backup system (on iPhone). If you have MTLS enabled, it does not work as it does not present the client certificate when connecting to the server (via proxy). --

I see the next build has deprecation for the old timeline, which means no more backups with MTLS??

I do hope the new backup picks up MTLS :)

@bunsenhd commented on GitHub (Sep 26, 2025): @jonkerw85 iOS and self signed is a real pain as they are never trusted by the OS unless you explicitly trust in Certificate Trust Settings... using a wildcard LE cert on your proxy, this is the way :). On my setup, I use a wildcard LE certificate for the proxy, and a Private CA for MTLS. My problem is with the new backup system (on iPhone). If you have MTLS enabled, it does not work as it does not present the client certificate when connecting to the server (via proxy). -- I see the next build has deprecation for the old timeline, which means no more backups with MTLS?? I do hope the new backup picks up MTLS :)
Author
Owner

@polomani commented on GitHub (Sep 26, 2025):

Reading this PR from @rovo89 https://github.com/immich-app/immich/pull/16403 I am getting the impression that a similar change has to be done to make mTLS with the beta timeline work on IOS.

There are parts of the app (presumably background workers) where HttpOverride has to be done natively.

I could probably write something (with the help of LLMs), but there is no way for me to make a build to test it out, unfortunately.

@polomani commented on GitHub (Sep 26, 2025): Reading this PR from @rovo89 https://github.com/immich-app/immich/pull/16403 I am getting the impression that a similar change has to be done to make mTLS with the beta timeline work on IOS. There are parts of the app (presumably background workers) where HttpOverride has to be done natively. I could probably write something (with the help of LLMs), but there is no way for me to make a build to test it out, unfortunately.
Author
Owner

@rovo89 commented on GitHub (Sep 26, 2025):

Also check https://github.com/immich-app/immich/pull/21459#issuecomment-3315212905 and following comments.

@rovo89 commented on GitHub (Sep 26, 2025): Also check https://github.com/immich-app/immich/pull/21459#issuecomment-3315212905 and following comments.
Author
Owner

@performation9 commented on GitHub (Oct 7, 2025):

Backups stopped working for me with the new timeline and mTLS as well on iOS. Is there any help needed in debugging? I am unfortunately no programmer and cannot attempt to fix the issue.

@performation9 commented on GitHub (Oct 7, 2025): Backups stopped working for me with the new timeline and mTLS as well on iOS. Is there any help needed in debugging? I am unfortunately no programmer and cannot attempt to fix the issue.
Author
Owner

@denysvitali commented on GitHub (Oct 8, 2025):

On the same note, is there any plan to finally support mTLS with CA verification in every part of the mobile codebase? For example, on Android it shouldn't be necessary to import the client credentials as PFX if they're already imported in the user trust store. Same applies for user trusted CAs.

Please do let me know if I should create a separate issue for this.

@denysvitali commented on GitHub (Oct 8, 2025): On the same note, is there any plan to finally support mTLS with CA verification in every part of the mobile codebase? For example, on Android it shouldn't be necessary to import the client credentials as PFX if they're already imported in the user trust store. Same applies for user trusted CAs. Please do let me know if I should create a separate issue for this.
Author
Owner

@kaaax0815 commented on GitHub (Oct 13, 2025):

I think a recap is needed as this thread is quite large.

mTLS does not properly work on android or ios.
using basic auth or custom headers does work on both platforms.

is that correct?

@kaaax0815 commented on GitHub (Oct 13, 2025): I think a recap is needed as this thread is quite large. mTLS does not properly work on android or ios. using basic auth or custom headers does work on both platforms. is that correct?
Author
Owner

@performation9 commented on GitHub (Oct 13, 2025):

I can confirm backups are not working with mTLS on iOS. logging in and viewing photos does work.

@performation9 commented on GitHub (Oct 13, 2025): I can confirm backups are not working with mTLS on iOS. logging in and viewing photos does work.
Author
Owner

@felixconsulting commented on GitHub (Oct 14, 2025):

I get the Trust anchor for certification path not valid error only when trying to play videos, and they refuse to play. Nothing else is impacted by this. I have a self signed certificate, the rootCA is imported to android, and the certificate my reverse proxy uses is under the rootCA android has. I'm using grapheneos with immich 2.0.1. Openssl says Verify return code: 19 (self-signed certificate in certificate chain) which suggests that it is an android app issue as opposed to a flawed certificate chain. I also have this issue with chora, a subsonic client which connects to a server with a similiar certificate chain, but no other apps I use have this problem. It seems I am the only person with this issue on immich or chora. I would appreciate any attention to this matter. Please not this ONLY applies to the mobile app, web video playback is fully functioning.

edit: This issue is likely identical to #5553, which is closed for some reason?

@felixconsulting commented on GitHub (Oct 14, 2025): I get the `Trust anchor for certification path not valid` error only when trying to play videos, and they refuse to play. Nothing else is impacted by this. I have a self signed certificate, the rootCA is imported to android, and the certificate my reverse proxy uses is under the rootCA android has. I'm using grapheneos with immich 2.0.1. Openssl says `Verify return code: 19 (self-signed certificate in certificate chain)` which suggests that it is an android app issue as opposed to a flawed certificate chain. I also have this issue with chora, a subsonic client which connects to a server with a similiar certificate chain, but no other apps I use have this problem. It seems I am the only person with this issue on immich or chora. I would appreciate any attention to this matter. Please not this ONLY applies to the mobile app, web video playback is fully functioning. edit: This issue is likely identical to #5553, which is closed for some reason?
Author
Owner

@imjustmatthew commented on GitHub (Oct 15, 2025):

It would be useful to mark these features in the app UI as experimental/unusable or link to this tracking issue on the advanced network settings page. These features are basically unusable and the app UI does not make that apparent.

@imjustmatthew commented on GitHub (Oct 15, 2025): It would be useful to mark these features in the app UI as experimental/unusable or link to this tracking issue on the advanced network settings page. These features are basically unusable and the app UI does not make that apparent.
Author
Owner

@mmomjian commented on GitHub (Oct 15, 2025):

@felixconsulting this is a know limitation with using your own RootCA, we don’t fully support this feature.

We will look into marking these features in the app as experimental. This was done in the past but seems to have been regressed.

@mmomjian commented on GitHub (Oct 15, 2025): @felixconsulting this is a know limitation with using your own RootCA, we don’t fully support this feature. We will look into marking these features in the app as experimental. This was done in the past but seems to have been regressed.
Author
Owner

@denysvitali commented on GitHub (Oct 16, 2025):

FYI: if we switch to OkHttp (see #22768 in draft), User CAs work perfectly fine. Same goes for mTLS - and the real issue seems to be that OkHttp doesn't support well websockets (or simply put, we need to rewrite a bit the interface so that we can use the OkHttp impl and the dart-io one).

@denysvitali commented on GitHub (Oct 16, 2025): FYI: if we switch to OkHttp (see #22768 in draft), User CAs work perfectly fine. Same goes for mTLS - and the real issue seems to be that OkHttp doesn't support well websockets (or simply put, we need to rewrite a bit the interface so that we can use the OkHttp impl and the dart-io one).
Author
Owner

@felixconsulting commented on GitHub (Oct 18, 2025):

@mmomjian It seems very hit or miss with android apps and my custom rootca, is there a way to just flick a switch and read those imported CAs like the default ones? it seems like thats more of the issue cause people with lets encrypt don't have this problem

@felixconsulting commented on GitHub (Oct 18, 2025): @mmomjian It seems very hit or miss with android apps and my custom rootca, is there a way to just flick a switch and read those imported CAs like the default ones? it seems like thats more of the issue cause people with lets encrypt don't have this problem
Author
Owner

@mmomjian commented on GitHub (Oct 18, 2025):

@mmomjian It seems very hit or miss with android apps and my custom rootca, is there a way to just flick a switch and read those imported CAs like the default ones? it seems like thats more of the issue cause people with lets encrypt don't have this problem

No, if there was a switch to flick we would not have this issue to begin with :P

@mmomjian commented on GitHub (Oct 18, 2025): > [@mmomjian](https://github.com/mmomjian) It seems very hit or miss with android apps and my custom rootca, is there a way to just flick a switch and read those imported CAs like the default ones? it seems like thats more of the issue cause people with lets encrypt don't have this problem No, if there was a switch to flick we would not have this issue to begin with :P
Author
Owner

@jeverling commented on GitHub (Oct 23, 2025):

I'm affected by problems with basic auth, the Android app isn't able to back up photos anymore. A single photo out of 25 new ones went throught actually, for the rest I get a 401 error, and the app retries them (indefinitely?).
This used to work before (at some point in the past it was broken too, but that got fixed).
Does anybody know which version broke this functionality again so I can downgrade?
And since we don't have individual tickets for the Experimental network issues, is someone maybe already working on this?

@jeverling commented on GitHub (Oct 23, 2025): I'm affected by problems with basic auth, the Android app isn't able to back up photos anymore. A single photo out of 25 new ones went throught actually, for the rest I get a 401 error, and the app retries them (indefinitely?). This used to work before (at some point in the past it was broken too, but that got fixed). Does anybody know which version broke this functionality again so I can downgrade? And since we don't have individual tickets for the Experimental network issues, is someone maybe already working on this?
Author
Owner

@Syoc commented on GitHub (Oct 25, 2025):

Hi. Can confirm that the iOS app does not use the client certificate for the /api/assets endpoint.
Regarding alternatives to mutual TLS authentication. A large part of keeping internet exposed infrastructure secure is managing attack surface. Exposing the app specific authentication logic, be that username and password or OIDC, is a much larger attack surface and not acceptable by a lot of people. A VPN connection is more comparable, but it is a lot to impose on other instance users. There are also power consumption issues on mobile devices. Users with existing VPN connection also have to handle complex routing setups.

@Syoc commented on GitHub (Oct 25, 2025): Hi. Can confirm that the iOS app does not use the client certificate for the /api/assets endpoint. Regarding alternatives to mutual TLS authentication. A large part of keeping internet exposed infrastructure secure is managing attack surface. Exposing the app specific authentication logic, be that username and password or OIDC, is a much larger attack surface and not acceptable by a lot of people. A VPN connection is more comparable, but it is a lot to impose on other instance users. There are also power consumption issues on mobile devices. Users with existing VPN connection also have to handle complex routing setups.
Author
Owner

@nolith commented on GitHub (Nov 17, 2025):

I should have read this before implementing mTLS and reworking my entire proxy setup.

So I moved to mTLS to relief my wife from running the VPN client, it works perfectly on browser and for general usage of the app, except it does not allow to upload images.

I managed to workaround that by tweaking my nginx config and remove the mTLS validation only for /api/assets

 - - [17/Nov/2025:18:24:51 +0000] "POST /api/auth/validateToken HTTP/1.1" 200 19 "-" "Immich_iOS_2.2.3"
 - - [17/Nov/2025:18:24:51 +0000] "POST /api/sync/ack HTTP/1.1" 204 0 "-" "Immich_iOS_2.2.3"
 - - [17/Nov/2025:18:25:03 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0"
 - - [17/Nov/2025:18:25:04 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0"
 - - [17/Nov/2025:18:25:04 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0"

From the log we can see the user agent is different between regular app usage and the background upload. Also, before redacting the IP addresses, I could dee that Immich_iOS_2.2.3 was using IPv4 while immich_mobile/235 was using IPv6.

@nolith commented on GitHub (Nov 17, 2025): I should have read this before implementing mTLS and reworking my entire proxy setup. So I moved to mTLS to relief my wife from running the VPN client, it works perfectly on browser and for general usage of the app, except it does not allow to upload images. I managed to workaround that by tweaking my nginx config and remove the mTLS validation only for `/api/assets` ``` - - [17/Nov/2025:18:24:51 +0000] "POST /api/auth/validateToken HTTP/1.1" 200 19 "-" "Immich_iOS_2.2.3" - - [17/Nov/2025:18:24:51 +0000] "POST /api/sync/ack HTTP/1.1" 204 0 "-" "Immich_iOS_2.2.3" - - [17/Nov/2025:18:25:03 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0" - - [17/Nov/2025:18:25:04 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0" - - [17/Nov/2025:18:25:04 +0000] "POST /api/assets HTTP/2.0" 201 64 "-" "immich_mobile/235 CFNetwork/3860.200.71 Darwin/25.1.0" ``` From the log we can see the user agent is different between regular app usage and the background upload. Also, before redacting the IP addresses, I could dee that `Immich_iOS_2.2.3` was using IPv4 while `immich_mobile/235` was using IPv6.
Author
Owner

@ZoTay commented on GitHub (Nov 18, 2025):

@nolith could you share your snippet of your nginx proxy to remove validation for that specific uri?

@ZoTay commented on GitHub (Nov 18, 2025): @nolith could you share your snippet of your nginx proxy to remove validation for that specific uri?
Author
Owner

@nolith commented on GitHub (Nov 18, 2025):

@nolith could you share your snippet of your nginx proxy to remove validation for that specific uri?

Sure, here is my config @ZoTay.

The key element are:

  1. setting ssl_verify_client optional;,
  2. adding if ($ssl_client_verify != SUCCESS) { return 418; } to location /,
  3. and an additional location for /api/assets.
server {
        listen 0.0.0.0:443 ssl ;
        listen [::0]:443 ssl ;
        server_name immich.example.com ;
        http2 on;
        # allow large file uploads
        client_max_body_size 50000M;
        # set timeout
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
        send_timeout       600s;
        ssl_client_certificate /nix/store/2y4qd4saia8gqzc428p9ff77vyc05ck3-ca-chain.crt;
        ssl_verify_client      optional;
        ssl_verify_depth 2;
        ssl_certificate /var/lib/acme/immich.example.com/fullchain.pem;
        ssl_certificate_key /var/lib/acme/immich.example.com/key.pem;
        ssl_trusted_certificate /var/lib/acme/immich.example.com/chain.pem;
        location / {
                if ($ssl_client_verify != SUCCESS) { return 418; }

                proxy_pass http://internal-immich.example.com/;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
        }

        location /api/assets {
                proxy_pass http://internal-immich.example.com/api/assets;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
                include /nix/store/rcwisbyj368bblylmp2gglsy50bzifng-nginx-recommended-proxy_set_header-headers.conf;
        }
}
@nolith commented on GitHub (Nov 18, 2025): > [@nolith](https://github.com/nolith) could you share your snippet of your nginx proxy to remove validation for that specific uri? Sure, here is my config @ZoTay. The key element are: 1. setting `ssl_verify_client optional;`, 2. adding ` if ($ssl_client_verify != SUCCESS) { return 418; }` to location `/`, 3. and an additional location for `/api/assets`. ``` server { listen 0.0.0.0:443 ssl ; listen [::0]:443 ssl ; server_name immich.example.com ; http2 on; # allow large file uploads client_max_body_size 50000M; # set timeout proxy_read_timeout 600s; proxy_send_timeout 600s; send_timeout 600s; ssl_client_certificate /nix/store/2y4qd4saia8gqzc428p9ff77vyc05ck3-ca-chain.crt; ssl_verify_client optional; ssl_verify_depth 2; ssl_certificate /var/lib/acme/immich.example.com/fullchain.pem; ssl_certificate_key /var/lib/acme/immich.example.com/key.pem; ssl_trusted_certificate /var/lib/acme/immich.example.com/chain.pem; location / { if ($ssl_client_verify != SUCCESS) { return 418; } proxy_pass http://internal-immich.example.com/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } location /api/assets { proxy_pass http://internal-immich.example.com/api/assets; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; include /nix/store/rcwisbyj368bblylmp2gglsy50bzifng-nginx-recommended-proxy_set_header-headers.conf; } } ```
Author
Owner

@denysvitali commented on GitHub (Nov 19, 2025):

FYI, the reason why you see different user agents and fail the mTLS part is because the HttpClient being used there is not the same one (global one), thus it's missing the configuration.

In #22768 I fixed that and use the keystore for mTLS

@denysvitali commented on GitHub (Nov 19, 2025): FYI, the reason why you see different user agents and fail the mTLS part is because the HttpClient being used there is not the same one (global one), thus it's missing the configuration. In #22768 I fixed that and use the keystore for mTLS
Author
Owner

@nolith commented on GitHub (Nov 19, 2025):

FYI, the reason why you see different user agents and fail the mTLS part is because the HttpClient being used there is not the same one (global one), thus it's missing the configuration.

In #22768 I fixed that and use the keystore for mTLS

Thanks for your work.

When I first looked at that PR I got the impression it was only fixing Android. Does it also handle iOS?

@nolith commented on GitHub (Nov 19, 2025): > FYI, the reason why you see different user agents and fail the mTLS part is because the HttpClient being used there is not the same one (global one), thus it's missing the configuration. > > In [#22768](https://github.com/immich-app/immich/pull/22768) I fixed that and use the keystore for mTLS Thanks for your work. When I first looked at that PR I got the impression it was only fixing Android. Does it also handle iOS?
Author
Owner

@denysvitali commented on GitHub (Nov 19, 2025):

It's only for Android (the mTLS part) but the changes related to using the global HTTP client are valid for all platforms

@denysvitali commented on GitHub (Nov 19, 2025): It's only for Android (the mTLS part) but the changes related to using the global HTTP client are valid for all platforms
Author
Owner

@waleedhad commented on GitHub (Nov 26, 2025):

I'm also experiencing failure to back up when using the beta/new timeline on iOS. Android devices and web works fine with the beta/new timeline enabled and same exact setup. My server and app are configured to use mTLS via Nginx as a reverse proxy. Switching back to the old timeline on iOS fixes the issue. I am strong proponent of mtls and I think its needed for security even though I have OIDC/OAuth configured and enabled as the only login option. I hope mtls is supported fully in the app.

@waleedhad commented on GitHub (Nov 26, 2025): I'm also experiencing failure to back up when using the beta/new timeline on iOS. Android devices and web works fine with the beta/new timeline enabled and same exact setup. My server and app are configured to use mTLS via Nginx as a reverse proxy. Switching back to the old timeline on iOS fixes the issue. I am strong proponent of mtls and I think its needed for security even though I have OIDC/OAuth configured and enabled as the only login option. I hope mtls is supported fully in the app.
Author
Owner

@webvictim commented on GitHub (Nov 26, 2025):

I have the same issue; mTLS does not work correctly using the new timeline on iOS. Works fine when I switch back to the old timeline. Also reported here: https://github.com/immich-app/immich/issues/22320#issuecomment-3437913692

@webvictim commented on GitHub (Nov 26, 2025): I have the same issue; mTLS does not work correctly using the new timeline on iOS. Works fine when I switch back to the old timeline. Also reported here: https://github.com/immich-app/immich/issues/22320#issuecomment-3437913692
Author
Owner

@crysxd commented on GitHub (Nov 27, 2025):

I discovered an issue with Basic Auth URLs. I run my Immich behind a reverse proxy which has a Basic Auth configured. The immich URL in the app is https://user:pw@myimmich.com

On iOS everything works perfectly. On Android, you can sign in and see pictures without issues but the uploads fail with a 401 error, seems like the URL is loosing the Basic Auth information for the upload

@crysxd commented on GitHub (Nov 27, 2025): I discovered an issue with Basic Auth URLs. I run my Immich behind a reverse proxy which has a Basic Auth configured. The immich URL in the app is https://user:pw@myimmich.com On iOS everything works perfectly. On Android, you can sign in and see pictures without issues but the uploads fail with a 401 error, seems like the URL is loosing the Basic Auth information for the upload
Author
Owner

@t-gh-ctrl commented on GitHub (Dec 30, 2025):

I confirm @crysxd 's findings above: with everything up to date as of writing (2.4.1), a reverse proxy (nginx) with basic auth configured, the immich app on iOS works fine with a user:pass@site address while uploads fail with 401 on android.
Despite configuring the server with user:pass@site, the server shows up as "site" in the app's settings (but maybe that's just cosmetic). I tried to set a user:pass@site as a custom server depending on the wifi network the phone is connected to, but it doesn't work either.
Temporary workarounds: set up a vpn (eg. wireguard) and configure the reverse proxy to skip basic auth for vpn clients; or skip basic auth depending on the client's properties like country networks and user agent (Immich_Android_.* / Dalvik/.*), and/or a custom proxy header set in the android app.

@t-gh-ctrl commented on GitHub (Dec 30, 2025): I confirm @crysxd 's findings above: with everything up to date as of writing (2.4.1), a reverse proxy (nginx) with basic auth configured, the immich app on iOS works fine with a user:pass@site address while uploads fail with 401 on android. Despite configuring the server with user:pass@site, the server shows up as "site" in the app's settings (but maybe that's just cosmetic). I tried to set a user:pass@site as a custom server depending on the wifi network the phone is connected to, but it doesn't work either. Temporary workarounds: set up a vpn (eg. wireguard) and configure the reverse proxy to skip basic auth for vpn clients; or skip basic auth depending on the client's properties like country networks and user agent (`Immich_Android_.*` / `Dalvik/.*`), and/or a custom proxy header set in the android app.
Author
Owner

@pippo73 commented on GitHub (Jan 3, 2026):

Hello all,
Since some weeks I'm moving my system from a FQDN to a tailnet
For this reason I have a CA selfsigned.
I've discovered that the ssl is not working with such certificates.
In the settings of the app, I've noticed that I should be able to add a CA to the app, but this features are not working even if present. How can I enable it?

Image
@pippo73 commented on GitHub (Jan 3, 2026): Hello all, Since some weeks I'm moving my system from a FQDN to a tailnet For this reason I have a CA selfsigned. I've discovered that the ssl is not working with such certificates. In the settings of the app, I've noticed that I should be able to add a CA to the app, but this features are not working even if present. How can I enable it? <img width="1080" height="2340" alt="Image" src="https://github.com/user-attachments/assets/96f08e9e-e243-4f16-9e54-bd9a010f06d0" />
Author
Owner

@jfly commented on GitHub (Jan 3, 2026):

@pippo73, I suggest reading through https://github.com/immich-app/immich/pull/19830 to learn more about self-signed certificates.

@jfly commented on GitHub (Jan 3, 2026): @pippo73, I suggest reading through https://github.com/immich-app/immich/pull/19830 to learn more about self-signed certificates.
Author
Owner

@ramanenka commented on GitHub (Jan 4, 2026):

On iOS widgets don't work when the reverse proxy in front of Immich is requiring client certificate despite specifying the client cert in "SSL client certificate" in advanced settings.

@ramanenka commented on GitHub (Jan 4, 2026): On iOS widgets don't work when the reverse proxy in front of Immich is requiring client certificate despite specifying the client cert in "SSL client certificate" in advanced settings.
Author
Owner

@bunsenhd commented on GitHub (Jan 30, 2026):

seems 2.5 onward has broken MTLS even further. You can 1) log in with MTLS enabled, 2) see local assets, but 3) Thumbnails fail, 4) remote images fail, and 5) backups fail with new time enabled. I guess with the new sync, MTLS is not in the mix.

Anyone else see the issues on IOS?

@bunsenhd commented on GitHub (Jan 30, 2026): seems 2.5 onward has broken MTLS even further. You can 1) log in with MTLS enabled, 2) see local assets, but 3) Thumbnails fail, 4) remote images fail, and 5) backups fail with new time enabled. I guess with the new sync, MTLS is not in the mix. Anyone else see the issues on IOS?
Author
Owner

@joutain commented on GitHub (Jan 30, 2026):

same observation as @bunsenhd , ios could see the assets from the server behind proxy and mtls, but can't show thumbnails, and click the photo not showing anything, in the log showing bunch of below. but when in local network with http://192.168.1.x:2283, all the phoeo, video, thumbnails showing properly again.

android seems to be working fine with proxy and mtls. ios specific problem

"Error loading image:
PlatformException(, Failed to decode image from request, null, null)"

@joutain commented on GitHub (Jan 30, 2026): same observation as @bunsenhd , ios could see the assets from the server behind proxy and mtls, but can't show thumbnails, and click the photo not showing anything, in the log showing bunch of below. but when in local network with http://192.168.1.x:2283, all the phoeo, video, thumbnails showing properly again. android seems to be working fine with proxy and mtls. ios specific problem "Error loading image: PlatformException(, Failed to decode image from request, null, null)"
Author
Owner

@krikk commented on GitHub (Jan 30, 2026):

seems 2.5 onward has broken MTLS even further. You can 1) log in with MTLS enabled, 2) see local assets, but 3) Thumbnails fail, 4) remote images fail, and 5) backups fail with new time enabled. I guess with the new sync, MTLS is not in the mix.

i still have the "old" timeline and all of the above seem to work for me (newest iOS)

@krikk commented on GitHub (Jan 30, 2026): > seems 2.5 onward has broken MTLS even further. You can 1) log in with MTLS enabled, 2) see local assets, but 3) Thumbnails fail, 4) remote images fail, and 5) backups fail with new time enabled. I guess with the new sync, MTLS is not in the mix. i still have the "old" timeline and all of the above seem to work for me (newest iOS)
Author
Owner

@Steven-- commented on GitHub (Jan 30, 2026):

I see exactly the same issues reported by @bunsenhd but on Android using Immich 2.5.1.

I am logged in, but thumbnails and remote images fail. I also cannot upload any local asset. I'm using the new timeline (I haven't tried the old one with this newer version).

@Steven-- commented on GitHub (Jan 30, 2026): I see exactly the same issues reported by @bunsenhd but on Android using Immich 2.5.1. I am logged in, but thumbnails and remote images fail. I also cannot upload any local asset. I'm using the new timeline (I haven't tried the old one with this newer version).
Author
Owner

@denysvitali commented on GitHub (Feb 1, 2026):

Yes - people - the problem is that the new timeline introduced a new HTTP client that doesn't have the same settings as the client that gets configured for the old timeline.

Server logs show this behavior too, since the User-Agent switches depending on the code path.
IMHO https://github.com/immich-app/immich/pull/22768 should have been merged and this problem would have been fixed, but the authors of this project don't seem to care about this particular issue.

I'm sorry - I kind of stopped using Immich after this 🙃

TL;DR: the real problem is that the codebase uses 3 / 4 different HTTP clients, and only one or two of them are configured w/ mTLS support / User-Agent

@denysvitali commented on GitHub (Feb 1, 2026): Yes - people - the problem is that the new timeline introduced a new HTTP client that doesn't have the same settings as the client that gets configured for the old timeline. Server logs show this behavior too, since the User-Agent switches depending on the code path. IMHO https://github.com/immich-app/immich/pull/22768 should have been merged and this problem would have been fixed, but the authors of this project don't seem to care about this particular issue. I'm sorry - I kind of stopped using Immich after this 🙃 TL;DR: the real problem is that the codebase uses 3 / 4 different HTTP clients, and only one or two of them are configured w/ mTLS support / User-Agent
Author
Owner

@mertalev commented on GitHub (Feb 2, 2026):

Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall.

@mertalev commented on GitHub (Feb 2, 2026): Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall.
Author
Owner

@Daniel-dev22 commented on GitHub (Feb 2, 2026):

Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall.

Is mtls on Android also being looked at?

@Daniel-dev22 commented on GitHub (Feb 2, 2026): > Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall. Is mtls on Android also being looked at?
Author
Owner

@mertalev commented on GitHub (Feb 2, 2026):

Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall.

Is mtls on Android also being looked at?

mTLS should already work on Android. What might not be working is the "allow self-signed certificates" option, meaning you might need to add the CA cert to the OS store.

@mertalev commented on GitHub (Feb 2, 2026): > > Sorry for the inconvenience. We're working on fixing mTLS on iOS. This is a part of a transition toward using only one client across the app, meaning one place to configure these settings and a smoother experience overall. > > Is mtls on Android also being looked at? > mTLS should already work on Android. What might not be working is the "allow self-signed certificates" option, meaning you might need to add the CA cert to the OS store.
Author
Owner

@jfly commented on GitHub (Feb 2, 2026):

What might not be working is the "allow self-signed certificates" option,

There is an (pretty dangerous) "allow any certificate feature. That works on Android, but I wouldn't use it myself.

meaning you might need to add the CA cert to the OS store.

This does not work on Android. Or, more accurately, there are 2 types of certificates in the Android OS store: "system certificates" and "user-installed certificates". System certificates work, but require a rooted device to add new ones. User-installed certificates do not work. #19830 made them work, but unfortunately that PR was rejected.

@jfly commented on GitHub (Feb 2, 2026): > What might not be working is the "allow self-signed certificates" option, There is an (pretty dangerous) "allow *any* certificate feature. That works on Android, but I wouldn't use it myself. > meaning you might need to add the CA cert to the OS store. This does not work on Android. Or, more accurately, there are 2 types of certificates in the Android OS store: "system certificates" and "user-installed certificates". System certificates work, but require a rooted device to add new ones. User-installed certificates do not work. #19830 made them work, but unfortunately that PR was rejected.
Author
Owner

@mertalev commented on GitHub (Feb 2, 2026):

System certificates work, but require a rooted device to add new ones. User-installed certificates do not work.

Hmm, changing the network security configuration to trust user-installed certs should be enough to fix that, right?

@mertalev commented on GitHub (Feb 2, 2026): > System certificates work, but require a rooted device to add new ones. User-installed certificates do not work. Hmm, changing the network security configuration to trust user-installed certs should be enough to fix that, right?
Author
Owner

@jfly commented on GitHub (Feb 2, 2026):

@mertalev, that works for the "native" android networking stack, but flutter's (actually, dart's) network stack does its own thing which is completely unaware of user-installed certificates on android. Please see my PR description (https://github.com/immich-app/immich/pull/19830) for more details and a link to upstream issue(s).

@jfly commented on GitHub (Feb 2, 2026): @mertalev, that works for the "native" android networking stack, but flutter's (actually, dart's) network stack does its own thing which is completely unaware of user-installed certificates on android. Please see my PR description (https://github.com/immich-app/immich/pull/19830) for more details and a link to upstream issue(s).
Author
Owner

@mertalev commented on GitHub (Feb 2, 2026):

@mertalev, that works for the "native" android networking stack, but flutter's (actually, dart's) network stack does its own thing which is completely unaware of user-installed certificates on android. Please see my PR description (https://github.com/immich-app/immich/pull/19830) for more details and a link to upstream issue(s).

Right, so it will work as long as the rest of the app also uses native clients.

@mertalev commented on GitHub (Feb 2, 2026): > @mertalev, that works for the "native" android networking stack, but flutter's (actually, dart's) network stack does its own thing which is completely unaware of user-installed certificates on android. Please see my PR description (https://github.com/immich-app/immich/pull/19830) for more details and a link to upstream issue(s). Right, so it will work as long as the rest of the app also uses native clients.
Author
Owner

@jfly commented on GitHub (Feb 2, 2026):

Yes. I was trying to make it clear what the current state of things is. Looking forward to the status quo changing, though!

@jfly commented on GitHub (Feb 2, 2026): Yes. I was trying to make it clear what the current state of things is. Looking forward to the status quo changing, though!
Author
Owner

@alextran1502 commented on GitHub (Feb 2, 2026):

Hello, just want to jump in here. Mert's been heads down on this and putting in real effort to get it sorted. I appreciate your patience while he works through it.

I get that it's frustrating when something breaks, but the networking feature is marked experimental for a reason: it's still evolving and not guaranteed to be stable, as we are still working through the tech debt in the mobile codebase. That's the trade-off with opting into experimental features early.

Self-hosted networking setups vary widely, and while we test against common configurations, we're still working through edge cases. That's just where we're at with this one.

As for not caring, we definitely do. We're just a small team trying to juggle a lot, and sometimes things take longer than any of us would like. I wish we had more people with deep networking knowledge to throw at this. We're doing what we can.

Thanks for understanding, and please help keep the discussion pleasant for all sides involved.

@alextran1502 commented on GitHub (Feb 2, 2026): Hello, just want to jump in here. Mert's been heads down on this and putting in real effort to get it sorted. I appreciate your patience while he works through it. I get that it's frustrating when something breaks, but the networking feature is marked experimental for a reason: it's still evolving and not guaranteed to be stable, as we are still working through the tech debt in the mobile codebase. That's the trade-off with opting into experimental features early. Self-hosted networking setups vary widely, and while we test against common configurations, we're still working through edge cases. That's just where we're at with this one. As for not caring, we definitely do. We're just a small team trying to juggle a lot, and sometimes things take longer than any of us would like. I wish we had more people with deep networking knowledge to throw at this. We're doing what we can. Thanks for understanding, and please help keep the discussion pleasant for all sides involved.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: immich-app/immich#5129