Skip to content

Android WSS fails with CertificateBundleLoadFailure in release build #24

@pierroo

Description

@pierroo

On Android release builds, Colyseus Godot native clients may fail to connect to wss:// endpoints with:

error 0: CertificateBundleLoadFailure

This happens before any room join succeeds. Multiple room/client instances fail with the same error, which suggests the failure is in TLS certificate bundle loading, not in room logic, auth, networking retries, or server protocol handling.

The issue appears device- or runtime-dependent: the same Android release build works for some users, but fails consistently for others.

Observed Behavior
In an Android release build using Godot 4.6.2-stable, all Colyseus rooms fail immediately after connecting:

status=connecting
status=error 0: CertificateBundleLoadFailure

The failure happens for multiple independent rooms using the same wss:// endpoint.

Diagnostics show:

os=Android
debug=false
endpoint=wss://example.com
last_error_code=0
last_error_message=CertificateBundleLoadFailure
connected=false

Expected Behavior
A Colyseus client using a valid public TLS endpoint should load CA roots and complete the WSS handshake, either by:

using Android system trust roots,
using the embedded Mozilla CA bundle,
or honoring network/tls/certificate_bundle_override.
Important Finding
The Android .so appears to include embedded Mozilla CA PEM data and strings such as:

Using bundled Mozilla CA certificates
network/tls/certificate_bundle_override
CertificateBundleLoadFailure
BEGIN CERTIFICATE

However, on affected Android release builds the client still fails with CertificateBundleLoadFailure.

Workaround Attempted
A client-side bootstrap was added before creating any Colyseus client:

const SOURCE_CERT_BUNDLE := "res://assets/certs/cacert.pem"
const USER_CERT_BUNDLE := "user://tls/cacert.pem"

DirAccess.make_dir_recursive_absolute("user://tls")

var source := FileAccess.open(SOURCE_CERT_BUNDLE, FileAccess.READ)
var data := source.get_buffer(source.get_length())

var target := FileAccess.open(USER_CERT_BUNDLE, FileAccess.WRITE)
target.store_buffer(data)

ProjectSettings.set_setting(
	"network/tls/certificate_bundle_override",
	USER_CERT_BUNDLE
)

The CA bundle is included in mobile exports:

include_filter="assets/certs/*.pem"
Diagnostics confirmed the file was copied successfully:

tls_override enabled=true configured=true path=user://tls/cacert.pem bytes=189462 error=

However, affected Android clients still failed with:

error 0: CertificateBundleLoadFailure

This suggests that either network/tls/certificate_bundle_override is not honored on Android in this path, user:// paths are not accepted by the native extension, or TLS/certificate initialization happens before the override is read.

Proposed Fix
In the native Godot Colyseus extension:

Ensure Android release builds can reliably load CA roots for WSS.
Prefer Android system trust store when available.
If using embedded Mozilla CA data, ensure it is available and parsed correctly on Android release builds.
Ensure network/tls/certificate_bundle_override is documented and works on Android, including what path formats are supported.
Consider logging the exact certificate source attempted and the low-level parse/open failure when CertificateBundleLoadFailure occurs.
Why This Matters
The failure occurs before Colyseus room logic runs, so frontend retry/reconnect logic cannot recover from it. The user only sees multiplayer unavailable, while the root cause is TLS initialization/certificate loading.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions