{"record":{"id":"738c8688833ab6de","repo":"apache/shenyu","slug":"at-least-one-certificate-is-required","errorCode":null,"errorMessage":"At least one certificate is required","messagePattern":"At least one certificate is required","errorType":"exception","errorClass":"ShenyuException","httpStatus":null,"severity":"error","filePath":"shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java","lineNumber":105,"sourceCode":"    @ConditionalOnProperty(value = \"shenyu.netty.http.web-server-factory-enabled\", havingValue = \"true\", matchIfMissing = true)\n    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(final ObjectProvider<NettyHttpProperties> properties,\n                                                                       final ObjectProvider<ShenyuSniAsyncMapping> shenyuSniAsyncMappingProvider,\n                                                                       final ObjectProvider<TcpSslContextSpec> tcpSslContextSpecs) {\n        NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();\n        NettyHttpProperties nettyHttpProperties = Optional.ofNullable(properties.getIfAvailable()).orElse(new NettyHttpProperties());\n        webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer(nettyHttpProperties, httpServer -> {\n            HttpServer server = httpServer;\n            // Configure sni certificates\n            NettyHttpProperties.SniProperties sniProperties = nettyHttpProperties.getSni();\n            if (sniProperties.getEnabled()) {\n                ShenyuSniAsyncMapping shenyuSniAsyncMapping = shenyuSniAsyncMappingProvider.getIfAvailable();\n                if (Objects.isNull(shenyuSniAsyncMapping)) {\n                    throw new ShenyuException(\"Can not find shenyuSniAsyncMapping bean\");\n                }\n                if (\"manual\".equals(sniProperties.getMod())) {\n                    List<SslCrtAndKeyFile> sslCrtAndKeyFiles = sniProperties.getCertificates();\n                    if (CollectionUtils.isEmpty(sslCrtAndKeyFiles)) {\n                        throw new ShenyuException(\"At least one certificate is required\");\n                    }\n\n                    // Use the first certificate as the default certificate (this default certificate will not actually be used)\n                    List<SslCrtAndKeyFile> certificates = sslCrtAndKeyFiles;\n                    for (SslCrtAndKeyFile certificate : certificates) {\n                        try {\n                            shenyuSniAsyncMapping.addSslCertificate(certificate);\n                        } catch (IOException e) {\n                            LOG.error(\"add certificate error\", e);\n                        }\n                    }\n\n                    SslCrtAndKeyFile defaultCert = certificates.get(0);\n                    TcpSslContextSpec defaultSpec = TcpSslContextSpec.forServer(new File(defaultCert.getKeyCertChainFile()),\n                            new File(defaultCert.getKeyFile()));\n                    \n                    server = server.secure(spec -> spec.sslContext(defaultSpec)\n                                .setSniAsyncMappings(shenyuSniAsyncMapping), false);","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java#L87-L123","documentation":"In 'manual' SNI mode the operator must list TLS certificates (cert+key file pairs) in configuration. If SNI is enabled, mod=manual, and the certificates list is empty or absent, the server cannot establish any SNI mapping and startup fails.","triggerScenarios":"shenyu.server.netty.sni.enabled=true with shenyu.server.netty.sni.mod=manual but shenyu.server.netty.sni.certificates is empty, missing, or only contains entries that failed to deserialize into SslCrtAndKeyFile.","commonSituations":"YAML indentation mistake placing certificates under the wrong key; enabling SNI before provisioning cert files; certificate entries typed with wrong property names so the list binds empty.","solutions":["Add at least one certificate entry under shenyu.server.netty.sni.certificates with valid crt/key file paths","Verify the YAML structure puts certificates under sni.certificates with correct field names","Check that referenced cert/key files exist and are readable (a later step loads them)"],"exampleFix":"# before\nshenyu:\n  server:\n    netty:\n      sni:\n        enabled: true\n        mod: manual\n# after\nshenyu:\n  server:\n    netty:\n      sni:\n        enabled: true\n        mod: manual\n        certificates:\n          - crt: /etc/shenyu/tls/server.crt\n            key: /etc/shenyu/tls/server.key","handlingStrategy":"validation","validationCode":"if (sni.isEnabled() && \"manual\".equals(sni.getMod())\n        && (sni.getCertificates() == null || sni.getCertificates().isEmpty())) {\n    throw new IllegalArgumentException(\"sni.certificates must list at least one crt/key pair in manual mode\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    SpringApplication.run(GatewayApplication.class, args);\n} catch (Exception e) {\n    if (rootCauseOf(e, ShenyuException.class).map(x -> x.getMessage().contains(\"certificate is required\")).orElse(false)) {\n        log.error(\"Add sni.certificates entries or switch sni.mod to k8s\");\n    } else throw e;\n}","preventionTips":["Validate the full SNI block in config CI before deploy","Provision certificates before enabling SNI","Bind the certificates list to a typed config class to catch YAML indentation errors early"],"tags":["netty","sni","ssl","configuration","empty-list"],"backgroundTag":"missing-required-config-field","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}