{"record":{"id":"344605b3981974bb","repo":"golang-migrate/migrate","slug":"failed-to-append-pem-344605","errorCode":null,"errorMessage":"failed to append PEM","messagePattern":"failed to append PEM","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"database/ql/ql.go","lineNumber":26,"sourceCode":"\tnurl \"net/url\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/database\"\n\t_ \"modernc.org/ql/driver\"\n)\n\nfunc init() {\n\tdatabase.Register(\"ql\", &Ql{})\n}\n\nvar DefaultMigrationsTable = \"schema_migrations\"\nvar (\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrAppendPEM      = fmt.Errorf(\"failed to append PEM\")\n)\n\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n}\n\ntype Ql struct {\n\tdb       *sql.DB\n\tisLocked atomic.Bool\n\n\tconfig *Config\n}\n\nfunc WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {\n\tif config == nil {\n\t\treturn nil, ErrNilConfig\n\t}","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/golang-migrate/migrate/blob/01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a/database/ql/ql.go#L8-L44","documentation":"ErrAppendPEM is returned by the ql/mysql config path when rootCertPool.AppendCertsFromPEM(pem) fails, i.e. the bytes read for the CA certificate were not a valid PEM-encoded certificate. The driver cannot build a TLS trust pool from the provided file or inline value, so it refuses to configure a secure connection rather than silently trusting nothing.","triggerScenarios":"Providing x-tls-ca / cert path whose file content is not PEM (DER binary, HTML error page, empty file, truncated download); the same sentinel is returned by mysql's urlToMySQLConfig when the x-tls-ca file's bytes fail AppendCertsFromPEM.","commonSituations":"Pointing x-tls-ca at a DER-encoded certificate instead of PEM; an expired/rotated cert file replaced by a proxy error page; forgetting to mount the secret in Kubernetes so the file is empty; concatenating keys into the CA file incorrectly.","solutions":["Convert the certificate to PEM format (openssl x509 -inform DER -in cert.der -out cert.pem) and re-run","Verify the file exists, is readable by the process, and starts with '-----BEGIN CERTIFICATE-----'","Download the CA bundle again and confirm the checksum; ensure the mounted secret contains the cert, not an error page"],"exampleFix":"// before\nca, _ := os.ReadFile(\"cert.der\") // DER-encoded\n// after\nca, _ := os.ReadFile(\"cert.pem\") // PEM: \"-----BEGIN CERTIFICATE-----\"\nif !strings.Contains(string(ca), \"BEGIN CERTIFICATE\") {\n    return errors.New(\"CA file is not PEM-encoded\")\n}","handlingStrategy":"validation","validationCode":"func validateCAFile(path string) error {\n    pem, err := os.ReadFile(path)\n    if err != nil {\n        return err\n    }\n    if !bytes.Contains(pem, []byte(\"-----BEGIN CERTIFICATE-----\")) {\n        return fmt.Errorf(\"%s is not PEM-encoded\", path)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := validateCAFile(caPath); err != nil {\n    return fmt.Errorf(\"invalid TLS CA: %w\", err)\n}\n// otherwise connect and handle ErrAppendPEM explicitly:\nif errors.Is(err, mysql.ErrAppendPEM) {\n    return fmt.Errorf(\"CA file %s is not PEM-encoded\", caPath)\n}","preventionTips":["Always distribute certificates in PEM (Base64) format, not DER","Verify mounted secrets actually contain cert bytes and start with -----BEGIN CERTIFICATE-----","Run a TLS preflight check (openssl x509 -in cert.pem -noout) in CI/deploy hooks"],"tags":["tls","certificates","pem","configuration"],"backgroundTag":"invalid-pem-certificate","analyzedSha":"01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a","analyzedAt":"2026-09-02T19:38:29.671Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}