X25519: Security and TLS Elliptic Curve Support
Learn what X25519 does in TLS 1.3, how to verify support, configure groups safely, and troubleshoot negotiation failures.

Short answer: X25519 is a Diffie–Hellman key-agreement function defined by RFC 7748. It allows two TLS peers to derive shared secret material. X25519 does not authenticate either peer, encrypt application records, or replace a TLS certificate. Those properties come from the surrounding TLS protocol.
For TLS 1.3, the current specification is RFC 9846. A compliant implementation must support P-256 key exchange and should support X25519. In practice, X25519 is a common and recommended TLS 1.3 group, but it is not the only required group. Whether it is offered or selected depends on your TLS library, version, build, provider configuration, and the peer’s supported groups.
What X25519 actually does
X25519 is the Curve25519 variant of the XDH (elliptic-curve Diffie–Hellman) family. Each endpoint creates an ephemeral private value and derives a public value. After exchanging public values, both endpoints calculate the same shared secret. An observer who sees the public values should not be able to recover that secret under the algorithm’s security assumptions.
The result is key-agreement input, not a finished secure channel. TLS uses the result in its key schedule, binds it to the authenticated handshake transcript, and derives traffic keys for record encryption and integrity. Server authentication normally comes from a certificate and signature. Client authentication, if enabled, is a separate TLS feature.
| Question | X25519 answers |
|---|---|
| Does it agree on a secret? | Yes. That is its purpose. |
| Does it identify the peer? | No. Use TLS authentication and certificate validation. |
| Is it a cipher suite? | No. It is a key-exchange group. TLS 1.3 cipher suites describe symmetric AEAD and hash choices separately. |
| Does it encrypt HTTP by itself? | No. The TLS record layer performs encryption after the handshake. |
Is X25519 supported in TLS?
Yes. TLS 1.3 defines negotiation of elliptic-curve groups through the supported-groups and key-share extensions. RFC 9846 states: “A TLS-compliant application MUST support key exchange with secp256r1 (NIST P-256) and SHOULD support key exchange with X25519 [RFC7748].” This makes X25519 a recommended TLS 1.3 option, while P-256 remains the mandatory baseline.
RFC 9325 recommends supporting TLS 1.3 and preferring it when available, while retaining TLS 1.2 where interoperability requires it. TLS-version policy and group policy are separate decisions: enabling TLS 1.3 does not prove that X25519 is enabled, and enabling X25519 does not create an adequate protocol policy.
How TLS 1.3 negotiates X25519
- The client sends a
supported_groupslist containing groups it can use, such as X25519 and P-256. - The client may send a
key_sharefor one or more groups. Many implementations start with X25519 and P-256. - The server selects a mutually supported group and sends its key share.
- If the server cannot use any key share already sent but sees another mutual group, it can send a HelloRetryRequest. The extra exchange adds a round trip.
- Both sides derive the shared secret and continue the authenticated TLS 1.3 handshake.
The exact initial key-share list and preference order are implementation details. OpenSSL’s project guidance describes X25519 and P-256 as common initial shares and notes that an unsupported selection can cause an additional round trip. Always check the deployed library version and effective configuration instead of assuming a default.

Verify X25519 support with OpenSSL
Start by identifying the binary and library version on the machine that makes or accepts the connection:
openssl version -a
openssl ciphers -v | head
For a TLS 1.3 server, request X25519 explicitly:
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -groups X25519 -brief
If the server and your OpenSSL build can negotiate it, the brief output reports a TLS 1.3 session and the negotiated group. Test a fallback group in the same way:
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -groups P-256 -brief
To test a preference list, provide both groups. The selected result depends on the peer and the client’s group ordering:
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -groups X25519:P-256 -brief
For a server you administer, inspect the effective configuration rather than only the configuration file. OpenSSL group behavior and defaults vary by release; the project documentation records version-sensitive changes, including newer hybrid groups in OpenSSL 3.5. See the OpenSSL TLS 1.3 guidance for the release you deploy.
Check from cURL, Python, and Node.js
cURL
First check whether your cURL build exposes a curve option:
curl --help all | grep -i curve
When supported, force TLS 1.3 and request X25519:
curl --tlsv1.3 --curves X25519 https://example.com/ -v
Because cURL options depend on the linked TLS backend, treat an “unknown option” error as a build capability issue. Repeat the test with P-256 to separate an X25519 problem from a general TLS 1.3 problem.
Python
Python’s standard ssl module exposes the negotiated TLS version and cipher, but it does not provide a portable API for setting the TLS 1.3 supported-groups list. Use it to confirm the protocol, then use OpenSSL tooling or your library’s native API for group selection:
import socket
import ssl
host = "example.com"
context = ssl.create_default_context()
with socket.create_connection((host, 443), timeout=10) as raw:
with context.wrap_socket(raw, server_hostname=host) as tls:
print("TLS version:", tls.version())
print("Cipher:", tls.cipher())
print("Peer:", tls.getpeercert()["subject"])
This confirms that certificate validation and a TLS session succeeded. It does not prove that X25519 was selected; obtain negotiated-group details from verbose TLS diagnostics or server logs.
Node.js
Node.js exposes an ecdhCurve option through its TLS stack. Availability and accepted names depend on the Node.js and OpenSSL versions:
import tls from "node:tls";
const socket = tls.connect({
host: "example.com",
port: 443,
servername: "example.com",
minVersion: "TLSv1.3",
ecdhCurve: "X25519:P-256"
}, () => {
console.log("TLS:", socket.getProtocol());
console.log("Cipher:", socket.getCipher());
socket.end();
});
socket.on("error", (error) => {
console.error(error.message);
});
If Node rejects the option or cannot create a context, inspect process.version and the bundled OpenSSL version. Keep P-256 in the list unless you have verified that every required peer supports X25519.
Security requirements and implementation cautions
- Use an authenticated protocol. Raw X25519 does not authenticate a peer. In TLS, validate the certificate and hostname and preserve the handshake transcript protections.
- Handle the all-zero output. RFC 7748 discusses the possibility of an all-zero shared result and says applications may check for it and abort. A higher-level protocol can impose stricter behavior. Follow the TLS library’s documented handling rather than implementing ad hoc key agreement.
- Use constant-time code. RFC 7748 includes implementation guidance intended to reduce timing leakage. Prefer a maintained, audited cryptographic library over custom curve arithmetic.
- Keep key lifetimes appropriate. TLS 1.3 normally uses ephemeral key shares. Do not reuse private values across unrelated sessions unless the protocol and library explicitly require it.
- Separate compliance from interoperability. This dossier does not establish FIPS validation, jurisdictional acceptance, or a particular organization’s approved algorithm list. Check your applicable policy.
X25519 versus P-256
| Axis | X25519 | P-256 |
|---|---|---|
| Current TLS 1.3 status | Recommended support in RFC 9846 | Mandatory support in RFC 9846 |
| Primary use | Diffie–Hellman key agreement | Elliptic-curve key agreement in TLS; P-256 is also widely used for signatures in other contexts |
| Deployment question | Is the group enabled by this library and build? | Is the required baseline enabled and mutually supported? |
| Interoperability role | Offer when peers support it | Retain as a broad compatibility fallback |
Do not turn this into an unsupported speed or “bit strength” ranking. The standards reviewed here do not provide a universal performance comparison. Select groups according to your library support, peer population, security policy, and operational evidence.
Configuration checklist for production
- Record the TLS library, version, provider or backend, and build flags.
- Require TLS 1.3 where your compatibility policy allows it; retain TLS 1.2 only for identified interoperability needs.
- Offer both X25519 and P-256 unless your peer inventory proves one is unnecessary.
- Confirm the configured supported-groups list on both client and server.
- Capture negotiated protocol and group in diagnostic logs without recording private key material.
- Test certificate validation, hostname validation, session resumption, proxies, and load balancers separately.
- Retest after library upgrades because defaults and available groups can change.

Common errors and fixes
“no suitable key share” or handshake failure
The peers have no mutually usable group, or the server disallows the client’s offered key share. Add X25519 and P-256 to both supported-groups policies, then verify that the deployed binary actually includes them.
HelloRetryRequest appears unexpectedly
The client did not send a key share for the server’s preferred mutual group. Offer the expected initial shares, or accept the extra round trip when compatibility matters more than latency.
“unknown group” or “unsupported curve”
The command syntax, TLS backend, or library version does not recognize X25519. Check the version and provider configuration; do not infer support from the operating system alone.
TLS 1.3 works but X25519 is not selected
TLS 1.3 and X25519 are separate settings. Inspect the negotiated group and configured preference order. The peer may prefer P-256, or X25519 may be disabled.
Certificate verification fails after enabling X25519
Group negotiation does not replace authentication. Fix the trust store, hostname, certificate chain, or system clock independently.
Intermittent failures behind a load balancer
Different frontends may run different TLS libraries or group policies. Compare negotiated groups and versions per endpoint and align their configurations.
Performance, reliability, and cost considerations
Group choice affects handshake compatibility and, when a retry is needed, round trips. Avoid claiming a universal X25519 speed advantage without measurements for your hardware, TLS backend, and traffic pattern. Connection reuse and TLS session resumption often matter more to application latency than changing one mutually supported group.
For reliability, keep a compatible fallback, monitor handshake failures by protocol and group, and test upgrades in a staging environment. A configuration that succeeds against one server does not prove that every client, proxy, CDN, or regional endpoint supports the same group.
Cryptographic negotiation itself has no per-request vendor charge. Your operational costs come from CPU, network round trips, observability, and engineering time. If you need visual evidence of TLS test pages or documentation for a runbook, you can capture those pages without maintaining a browser stack.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. It can be useful when you need a repeatable image of a TLS diagnostic page, standards document, or internal status view after doing the protocol checks above.
Use the same request from the ScreenshotNeo API documentation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and whether the shot was billed. Its MCP server includes take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and get 1,000 screenshots a month without a card.
FAQ
Is X25519 encryption?
No. It is key agreement. TLS uses its output with authenticated handshakes and record protection.
Must every TLS 1.3 implementation use X25519?
No. RFC 9846 requires P-256 and recommends X25519. A compliant implementation can negotiate another supported group.
Can I enable X25519 with a certificate setting?
No. Certificates authenticate identities; supported groups control key exchange. Configure and verify them separately.
Should I remove P-256 if X25519 works?
Usually keep P-256 for interoperability unless your tested peer population and policy justify removing it.
Does an X25519 handshake prove the server is secure?
No. Also verify TLS version, certificate validation, hostname checks, key logging policy, patch level, and application configuration.


