A Guide to Generating TLS Ed25519 (Elliptic Curve Cryptography) Certificates Using Private CA
Overview
This guide demonstrates how to set up secure TLS 1.3 communication using Ed25519 elliptic curve certificates and a private Certificate Authority (CA). It covers encrypted client-server communication with modern, efficient cryptographic standards — ideal for internal systems, microservices, and zero-trust network architectures.
What Is Ed25519 and How Does It Compare to RSA?
Ed25519 is a modern elliptic-curve signature algorithm that offers fast operations, smaller keys, and strong security while reducing operational overhead in your infrastructure.
Below is a summary of the key aspects in a comparison table:
Aspect | Ed25519 | RSA (2048) |
---|---|---|
Key Size | 256 bits | 2048 bits |
Signature Size | 64 bytes | ~256 bytes |
Security Level | ~128-bit (RSA-3072 equivalent) | ~112-bit |
Key Generation | 10–20x faster | Slower |
Signing Speed | 3–5x faster | Slower |
Verification Speed | 2–3x faster | Slower |
Bandwidth | Lower (smaller certs & signatures) | Higher |
Implementation | Simple, deterministic, no padding | Complex, padding required |
Side-Channel Resistance | Resistant | Susceptible if not carefully implemented |
Compatibility | Limited on legacy systems | Broad support |
Quantum Resistance | Not quantum-safe | Not quantum-safe |
Pros and Cons of Using Ed25519
Pros | Cons |
---|---|
Fast key generation, signing, and verification | Limited support on legacy systems |
Smaller certs & signatures reduce bandwidth | Not quantum-resistant, future migration needed |
Strong security (RSA-3072 equivalent) | |
Resistant to side-channel attacks | |
Simple, deterministic implementation | |
Lower CPU and memory usage |
Environment setup
I built this environment locally on Fedora 42 using libvirt
(Linux virtualisation API) and Vagrant
(VM automation tool) to provision 3 x AlmaLinux OS 9 nodes. The choice of virtualisation platform and OS are flexible — any setup that supports at least three VMs can be used, including cloud-based or alternative local environments.
- Virtualisation Platform:
libvirt
. - Orchestration VM tool:
Vagrant
. - VM OS: 3 x
AlmaLinux 9
. - Network: Private isolated network (
192.168.56.x
range).
Note: This setup can be replicated using any virtualization platform (VMware, VirtualBox, cloud VMs, or physical machines). The key requirement is having three separate systems that can communicate over a network.
node-03 — Certificate Authority (IP address: 192.168.56.103)
- Generates Ed25519 root certificate and signs CSRs.
node-02 — Server (IP address: 192.168.56.102)
- Generates Ed25519 private key and CSR.
- Receives signed certificate from CA.
- Hosts HTTPS service using NGINX with TLS 1.3.
node-01 — Client (192.168.56.101)
- Trusts CA certificate.
- Initiates TLS connection to the server and validates the ed25519 certificate.
- Verifies encrypted communication.
Architecture Flow Diagram
The diagram below illustrates the complete TLS 1.3 setup flow using Ed25519 and a private Certificate Authority (CA), showing each step across the client, server, and CA nodes.
For each step shown in the diagram, the corresponding implementation commands are provided in the sections below.
Detailed Implementation
The following implementation steps assume you are operating as the root
user in /root
directory.
Step 1–2: Private Root Certificate Authority Setup (almalinux9-node-03)
Create the private Root Certificate Authority (CA) that will issue certificated for TLS.
1# Switch to root shell with root environment
2sudo -i
3
4# Install OpenSSL
5dnf install -y openssl
6
7# Create CA directory structure
8mkdir -p ~/ca/{certs,crl,newcerts,private}
9chmod 700 ~/ca/private
10cd ~/ca
11
12# Create CA configuration file
13cat > openssl.cnf << 'EOF'
14[ ca ]
15default_ca = CA_default
16
17[ CA_default ]
18dir = /root/ca
19certs = $dir/certs
20crl_dir = $dir/crl
21new_certs_dir = $dir/newcerts
22database = $dir/index.txt
23serial = $dir/serial
24RANDFILE = $dir/private/.rand
25
26private_key = $dir/private/ca.key.pem
27certificate = $dir/certs/ca.cert.pem
28
29default_md = sha256
30name_opt = ca_default
31cert_opt = ca_default
32default_days = 365
33preserve = no
34policy = policy_strict
35
36[ policy_strict ]
37countryName = match
38stateOrProvinceName = match
39organizationName = match
40organizationalUnitName = optional
41commonName = supplied
42emailAddress = optional
43
44[ req ]
45default_bits = 2048
46distinguished_name = req_distinguished_name
47string_mask = utf8only
48default_md = sha256
49x509_extensions = v3_ca
50
51[ req_distinguished_name ]
52countryName = Country Name (2 letter code)
53stateOrProvinceName = State or Province Name
54localityName = Locality Name
550.organizationName = Organization Name
56organizationalUnitName = Organizational Unit Name
57commonName = Common Name
58emailAddress = Email Address
59
60[ v3_ca ]
61subjectKeyIdentifier = hash
62authorityKeyIdentifier = keyid:always,issuer
63basicConstraints = critical, CA:true
64keyUsage = critical, digitalSignature, cRLSign, keyCertSign
65
66[ server_cert ]
67basicConstraints = CA:FALSE
68subjectKeyIdentifier = hash
69authorityKeyIdentifier = keyid,issuer:always
70keyUsage = critical, digitalSignature, keyEncipherment
71extendedKeyUsage = serverAuth
72EOF
73
74# Initialize CA database
75touch index.txt
76echo 1000 > serial
77echo 1000 > crlnumber
78
79# Generate Ed25519 CA private key
80openssl genpkey -algorithm Ed25519 -out private/ca.key.pem
81chmod 400 private/ca.key.pem
82
83# Create self-signed CA certificate
84openssl req -config openssl.cnf -key private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem -subj "/C=GB/ST=England/L=Bristol/O=MyOrg/CN=MyCA"
85
86chmod 444 certs/ca.cert.pem
Step 3–4: Server Key & CSR (almalinux9-node-02)
Generate the server's private key and certificate signing request (CSR).
1# Switch to root shell with root environment
2sudo -i
3
4# Install OpenSSL and NGINX
5dnf install -y openssl nginx
6
7# Generate Ed25519 server private key
8openssl genpkey -algorithm Ed25519 -out server.key.pem
9
10# Create certificate signing request
11openssl req -new -key server.key.pem -out server.csr -subj "/C=GB/ST=England/L=Bristol/O=MyOrg/CN=almalinux9-node-02"
Step 5: CSR File Transfer (node-02 → node-03)
Transfer the server's CSR to the CA for signining
1# On almalinux9-node-02 (server)
2# Start temporary HTTP server to share the CSR
3python3 -m http.server 8000
Step 6–7: Certificate Signing (almalinux9-node-03)
Download and sign the CSR with the CA private key.
1# Switch to root shell with root environment
2sudo -i
3
4# On almalinux9-node-03 (CA)
5cd ~/ca
6
7# Download CSR from node-02
8wget http://192.168.56.102:8000/server.csr # 192.168.56.102 is node-02 (server) IP
9
10# Sign the server certificate
11openssl ca -config openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in server.csr -out server.cert.pem
12
13# Start HTTP server to distribute signed certificates to other nodes
14python3 -m http.server 8000
Step 8–9: Certificate Distribution (node-03 → node-02)
Download the signed certificate and CA certificate to the server.
1# On almalinux9-node-02
2
3# Download signed server certificate from CA
4wget http://192.168.56.103:8000/server.cert.pem # 192.168.56.103 is node-03 (CA) IP
5
6# Download CA certificate
7wget http://192.168.56.103:8000/certs/ca.cert.pem # 192.168.56.103 is node-03 (CA) IP
Step 10: Certificate Verification (almalinux9-node-02)
Verify the signed certificate is valid and properly configured before using it with NGINX.
1# 1. Verify certificate was signed by your CA
2openssl verify -CAfile ca.cert.pem server.cert.pem
3
4# Output expected
5server.cert.pem: OK
1# 2. Check certificate details
2openssl x509 -in server.cert.pem -noout -subject -issuer -dates -fingerprint
3
4# Output expected:
5subject=C=GB, ST=England, O=MyOrg, CN=almalinux9-node-02
6issuer=C=GB, ST=England, L=Bristol, O=MyOrg, CN=MyCA
7notBefore=Jul 14 20:00:35 2025 GMT
8notAfter=Jul 14 20:00:35 2026 GMT
9SHA1 Fingerprint=40:B3:BD:5D:3E:76:9C:FE:AE:08:92:5A:58:0B:53:35:CB:62:05:8E
1# 3. Confirm Ed25519 is being used
2openssl x509 -in server.cert.pem -noout -text | grep "Public Key Algorithm"
3
4# Output expected
5Public Key Algorithm: ED25519
1# 4. View complete certificate information (optional)
2openssl x509 -in server.cert.pem -text -noout
3
4# Output expected:
5Certificate:
6 Data:
7 Version: 3 (0x2)
8 Serial Number: 4096 (0x1000)
9 Signature Algorithm: ED25519
10 Issuer: C=GB, ST=England, L=Bristol, O=MyOrg, CN=MyCA
11 Validity
12 Not Before: Jul 14 20:00:35 2025 GMT
13 Not After : Jul 14 20:00:35 2026 GMT
14 Subject: C=GB, ST=England, O=MyOrg, CN=almalinux9-node-02
15 Subject Public Key Info:
16 Public Key Algorithm: ED25519
17 ED25519 Public-Key:
18 pub:
19 9c:03:54:87:8c:4b:b2:39:ca:4c:79:b7:39:6e:40:
20 eb:aa:3b:fb:4e:a7:7b:c1:7d:f9:3b:99:29:89:4b:
21 e6:68
22 X509v3 extensions:
23 X509v3 Basic Constraints:
24 CA:FALSE
25 X509v3 Subject Key Identifier:
26 48:6C:A7:A9:8B:72:29:1E:90:75:3B:6C:FE:4C:DF:75:1F:0F:59:5D
27 X509v3 Authority Key Identifier:
28 keyid:C4:72:73:3D:9D:CA:4A:CB:EF:77:D6:3F:D7:58:C9:B5:4A:A5:8C:DE
29 DirName:/C=GB/ST=England/L=Bristol/O=MyOrg/CN=MyCA
30 serial:43:D8:98:80:38:39:6E:1A:92:1E:31:CF:F8:1B:5B:E0:D0:BA:43:E7
31 X509v3 Key Usage: critical
32 Digital Signature, Key Encipherment
33 X509v3 Extended Key Usage:
34 TLS Web Server Authentication
35 Signature Algorithm: ED25519
36 Signature Value:
37 fb:4d:f9:d3:68:bb:ab:34:d0:c1:6c:e8:9f:fe:4a:57:57:64:
38 a4:2c:90:60:04:0b:c5:10:e8:2c:a6:99:eb:4a:25:c8:ba:39:
39 54:7c:60:bd:e4:0f:d3:f1:e6:99:c4:06:9b:8e:01:bf:f1:05:
40 14:62:0f:3d:cb:3b:6f:35:90:08
Certificate Validation Checklist
The outputs above confirm that our Ed25519 certificate setup is fully operational and secure:
- Certificate Verification: server.cert.pem: OK - Valid CA signature
- Identity Match: Subject CN=almalinux9-node-02 matches server hostname
- Trusted Issuer: Certificate signed by our private CA CN=MyCA
- Ed25519 Confirmed: Modern elliptic curve cryptography active
- TLS Ready: Proper extensions for HTTPS server authentication
Result: Server Certificate successfully validated - ready for NGINX TLS 1.3 deployment.
Step 11–12: NGINX Setup (almalinux9-node-02)
Configure NGINX with TLS 1.3 with the Ed25519 certificate.
1# Install certificates
2mkdir -p /etc/nginx/ssl
3cp server.cert.pem /etc/nginx/ssl/
4cp server.key.pem /etc/nginx/ssl/
5cp ca.cert.pem /etc/nginx/ssl/
6chmod 600 /etc/nginx/ssl/server.key.pem
7
8# Configure NGINX for TLS 1.3
9tee /etc/nginx/conf.d/https-server.conf << 'EOF'
10# HTTP Server (unencrypted) - for testing
11server {
12 listen 80;
13 server_name almalinux9-node-02;
14
15 location / {
16 return 200 "UNENCRYPTED HTTP Server - this data is visible!\n";
17 add_header Content-Type text/plain;
18 }
19
20 location /secret {
21 return 200 "SECRET DATA: password123 - credit card: 1234-5678-9012-3456\n";
22 add_header Content-Type text/plain;
23 }
24}
25
26# HTTPS Server (encrypted) - TLS 1.3
27server {
28 listen 443 ssl http2;
29 server_name almalinux9-node-02;
30
31 ssl_certificate /etc/nginx/ssl/server.cert.pem;
32 ssl_certificate_key /etc/nginx/ssl/server.key.pem;
33
34 ssl_protocols TLSv1.3;
35 ssl_prefer_server_ciphers off;
36
37 location / {
38 return 200 "HTTPS Server - TLS 1.3 + Ed25519 Encrypted!\n";
39 add_header Content-Type text/plain;
40 }
41
42 location /secret {
43 return 200 "ENCRYPTED SECRET DATA: admin_password=SuperSecret789 - credit_card=9876-5432-1098-7654 - ssn=123-45-6789 - bank_account=987654321\n";
44 add_header Content-Type text/plain;
45 }
46}
47EOF
48
49# Start NGINX
50systemctl start nginx
51systemctl enable nginx
52
53# Check NGINX status
54systemctl status nginz
Step 13: Client Setup (almalinux9-node-01)
Configure the client to trust the CA and establish secure TLS.
1# Install client tools
2dnf install -y openssl curl
3
4# Create certificate directory
5mkdir -p ~/certs
6
7# Download CA certificate
8wget http://192.168.56.103:8000/certs/ca.cert.pem -O ~/certs/ca.cert.pem
Steps 14–18: Secure Communication Phase
Test secure HTTPS access, inspect handshake, and verify traffic encryption.
1# 13. Test basic HTTPS connection first (establish that TLS works)
2curl --cacert ~/certs/ca.cert.pem https://almalinux9-node-02/secret
3
4# 14. Verify TLS handshake details
5curl -v --cacert ~/certs/ca.cert.pem https://almalinux9-node-02/
6
7# 15. Encrypted Packet Capture (HTTPS)
8echo "=== Capturing HTTPS Traffic (Encrypted) ==="
9tcpdump -i any -A -c 10 host almalinux9-node-02 and port 443 &
10sleep 2 # Give tcpdump time to start
11curl --cacert ~/certs/ca.cert.pem https://almalinux9-node-02/secret
12
13# 16. Plaintext Packet Capture (HTTP)
14echo "=== Capturing HTTP Traffic (Unencrypted) ==="
15tcpdump -i any -A -c 10 host almalinux9-node-02 and port 80 &
16sleep 2 # Give tcpdump time to start
17curl http://almalinux9-node-02/secret
Expected Results and Evidence
Test 1: Basic HTTPS Connection
1curl --cacert ~/certs/ca.cert.pem https://almalinux9-node-02/secret
Expected Output:
1ENCRYPTED SECRET DATA: admin_password=SuperSecret789 - credit_card=9876-5432-1098-7654 - ssn=123-45-6789 - bank_account=987654321
Client successfully receives decrypted data over secure TLS connection.
Test 2: TLS Handshake Verification
1curl -v --cacert ~/certs/ca.cert.pem https://almalinux9-node-02/
Key Output Indicators:
1* TLSv1.3 (OUT), TLS handshake, Client hello (1):
2* TLSv1.3 (IN), TLS handshake, Server hello (2):
3* TLSv1.3 (IN), TLS handshake, Certificate (11):
4* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
5* Server certificate:
6* subject: C=GB; ST=England; O=MyOrg; CN=almalinux9-node-02
7* issuer: C=GB; ST=England; L=Bristol; O=MyOrg; CN=MyCA
8* common name: almalinux9-node-02 (matched)
9* SSL certificate verify ok.
TLS 1.3 active, Ed25519 certificate verified, hostname matched.
Test 3: HTTPS Network Traffic Analysis
Network Capture (tcpdump):
1E..<..@.@.....8e..8f.h..Lv.o.........J.........
2..q.........
322:38:52.489995 eth1 In IP almalinux9-node-02.https > almalinux9-node-01.34408: Flags [S.], seq 133835979, ack 1282837360, win 65160, options [mss 1460,sackOK,TS val 1778032372 ecr 2642506154,nop,wscale 7], length 0
4E..<..@.@.H...8f..8e...h..,.Lv.p.....J.........
5i.....q.....
622:38:52.490029 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [.], ack 1, win 502, options [nop,nop,TS val 2642506155 ecr 1778032372], length 0
7E..4..@.@.....8e..8f.h..Lv.p..,......B.....
8..q.i...
922:38:52.493578 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [P.], seq 1:518, ack 1, win 502, options [nop,nop,TS val 2642506159 ecr 1778032372], length 517
10E..9..@.@.....8e..8f.h..Lv.p..,......G.....
11..q.i..............X-.:7...akx...PI[".w..0..&.C..#. ......p....A.c.o....Y...*........H.........,.0.......+./...#.'.
12... ...........=.<.5./...........k.g.9.3.....k.........almalinux9-node-02.........
13........................3t.........h2.http/1.1.........1.....". ........... .
14...................+........-.....3.&.$... o.."!......;.......T..H.|.....d6.............................................................................................................................................................................
1522:38:52.494024 eth1 In IP almalinux9-node-02.https > almalinux9-node-01.34408: Flags [.], ack 518, win 506, options [nop,nop,TS val 1778032376 ecr 2642506159], length 0
16E..4.|@.@..+..8f..8e...h..,.Lv.u.....B.....
17i.....q.
1822:38:52.494745 eth1 In IP almalinux9-node-02.https > almalinux9-node-01.34408: Flags [P.], seq 1:1015, ack 518, win 506, options [nop,nop,TS val 1778032377 ecr 2642506159], length 1014
19E..*.}@.@..4..8f..8e...h..,.Lv.u.....8.....
20i.....q.....z...v..../.{.z...&..t}....g.p.PJ.i.).t. ......p....A.c.o....Y...*.............+.....3.$... N${......s....k....O...A.*...i,g..........$a7.......4\u....h.Z..=...WrY/ar...v..........-H6.!tc.0.../.8.ca^.f.|...r....f|..Rm.....sE&n.4Yz3.:..y...1.[x|.....L'....Y..=...0....q.YiM.-b.3..z.....}..E+,.{..........Hdj.u.J.B...4M3bj.h........eisf..rS.R.G.K..7.!.K.VD._.otr.Y}..%..m#+3..).E......5U...M.r.9..3so....X
21..S ..'.X,q.........*_.&................Et..D...V........}.eK...~.Gj..I_..0ue.e..y.....B.V^S....}b6%.g3.P..s...e..w......,$.e.$.>oU....b..l.!;p.=.+...5....9bW2H1.$..........|...J;.)\.v!h`.C.sY.....
22WCGh.Xj..].....`...e....W.....(:E'......;.3.....@..B....K-....y&3.B.-.H...d.w6Z.YX[4..m....Xx.a.Xbx..VLQ1?Uw...._.....X....k^..
23..+.......1;Q..6.]N.1..1...SG..Zz...vM.2.P..@...}..u..,.....E..;.bQ...Q.............G.-..v...$...&.'.m.].n.Eb.YA1....Ye..ij....D..w. ez$.....K........3.x....bG...I.c_...*..$...U5..-.Sz!.36.......".......g!......E...l...........-..X<z.W...kgw..............3....g.....C.|O.K+.......A
2422:38:52.494777 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [.], ack 1015, win 524, options [nop,nop,TS val 2642506160 ecr 1778032377], length 0
25E..4..@.@.....8e..8f.h..Lv.u..0......B.....
26..q.i...
2722:38:52.496125 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [P.], seq 518:598, ack 1015, win 524, options [nop,nop,TS val 2642506161 ecr 1778032377], length 80
28E.....@.@.....8e..8f.h..Lv.u..0............
29..q.i.............E.KR...>xL...R....K..C.#.......).)..x....?rS.a..b..{.S....T*.t....{..<
3022:38:52.496230 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [P.], seq 598:644, ack 1015, win 524, options [nop,nop,TS val 2642506161 ecr 1778032377], length 46
31E..b..@.@.....8e..8f.h..Lv....0......p.....
32..q.i.......)9.C.P...Rb.~.....l. =....np.<.p'.J!|....
3322:38:52.496242 eth1 Out IP almalinux9-node-01.34408 > almalinux9-node-02.https: Flags [P.], seq 644:693, ack 1015, win 524, options [nop,nop,TS val 2642506161 ecr 1778032377], length 49
34E..e..@.@.....8e..8f.h..Lv....0......s.....
35..q.i.......,.,......;.hT.j..bPu.E.|@.....<.=gU2?=....z.
3610 packets captured
3727 packets received by filter
380 packets dropped by kernel
Evidence: Network traffic shows encrypted binary data - sensitive information is completely protected.
Test 4: HTTP Traffic Analysis (Comparison)
Network Capture (tcpdump):
1GET /secret HTTP/1.1
2Host: almalinux9-node-02
3User-Agent: curl/7.76.1
4
5HTTP/1.1 200 OK
6Server: nginx/1.20.1
7Content-Type: text/plain
8SECRET DATA: password123 - credit card: 1234-5678-9012-3456
Evidence: Network traffic exposes all sensitive data in plain text - completely insecure.
Security Comparison Results
Protocol (Client) | Experience | Network Security | Attacker Visibility |
---|---|---|---|
HTTPS (TLS 1.3 + Ed25519) | Normal data access | Fully encrypted | Encrypted gibberish only |
HTTP (Unencrypted) | Normal data access | No protection | All secrets visible |
Conclusion
This implementation demonstrates secure TLS 1.3 communication using Ed25519 elliptic curve cryptography and a private Certificate Authority (CA).
The key outcomes are summarised below:
- Ed25519: Strong cryptographic security with fast key generation and smaller signatures.
- TLS 1.3: Enforces modern encryption with forward secrecy and reduced handshake overhead.
- Private CA: Enables full control over certificate issuance and trust boundaries.
- Verified Encryption: Network traffic analysis confirms all sensitive data remains encrypted in transit.
Compared to RSA-based setups, Ed25519 simplifies key handling while improving performance and side-channel resistance—making it well-suited for secure internal systems and service-to-service communication.