Skip to main content
Wings supports SSL/TLS encryption for both the HTTP API and WebSocket connections. This page covers certificate configuration and best practices.

TLS Configuration

Wings uses industry-standard TLS configuration based on Cloudflare’s recommendations.

Default TLS Settings

Wings implements secure defaults for TLS connections:
// config/config.go:34-51
var DefaultTLSConfig = &tls.Config{
    NextProtos: []string{"h2", "http/1.1"},
    CipherSuites: []uint16{
        tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
        tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    },
    PreferServerCipherSuites: true,
    MinVersion:               tls.VersionTLS12,
    MaxVersion:               tls.VersionTLS13,
    CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
}
Key Features:
  • HTTP/2 Support: Enabled via h2 protocol
  • Modern Cipher Suites: Only secure ciphers (ECDHE with AES-GCM or ChaCha20-Poly1305)
  • TLS 1.2+: Minimum TLS 1.2, supports TLS 1.3
  • Forward Secrecy: All cipher suites provide perfect forward secrecy
  • Elliptic Curves: Prefers X25519 and P-256

Configuration File

Enable SSL in /etc/pterodactyl/config.yml:
api:
  host: 0.0.0.0
  port: 8080
  ssl:
    enabled: true
    cert: /etc/letsencrypt/live/wings.example.com/fullchain.pem
    key: /etc/letsencrypt/live/wings.example.com/privkey.pem
Configuration structure:
// config/config.go:83-88
Ssl struct {
    Enabled         bool   `json:"enabled" yaml:"enabled"`
    CertificateFile string `json:"cert" yaml:"cert"`
    KeyFile         string `json:"key" yaml:"key"`
}

Certificate Acquisition

Using Let’s Encrypt

The recommended way to obtain certificates is using Certbot with Let’s Encrypt.

Install Certbot

Ubuntu/Debian:
sudo apt update
sudo apt install certbot
CentOS/RHEL:
sudo yum install certbot

Obtain Certificate

Standalone Mode (Wings must be stopped):
sudo systemctl stop wings
sudo certbot certonly --standalone -d wings.example.com
sudo systemctl start wings
Webroot Mode (Wings can stay running):
sudo certbot certonly --webroot -w /var/www/html -d wings.example.com
DNS Challenge (for wildcard certificates):
sudo certbot certonly --manual --preferred-challenges dns -d wings.example.com

Certificate Locations

Certbot stores certificates in /etc/letsencrypt/live/:
  • Certificate: /etc/letsencrypt/live/wings.example.com/fullchain.pem
  • Private Key: /etc/letsencrypt/live/wings.example.com/privkey.pem
  • Chain: /etc/letsencrypt/live/wings.example.com/chain.pem
  • Certificate Only: /etc/letsencrypt/live/wings.example.com/cert.pem
Always use fullchain.pem for the certificate file, not cert.pem. The full chain includes intermediate certificates required for proper validation.

Auto-Renewal

Certbot automatically installs a systemd timer for renewal:
# Check renewal timer status
sudo systemctl status certbot.timer

# Test renewal (dry run)
sudo certbot renew --dry-run

Reload Wings After Renewal

Create a renewal hook to reload Wings:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-wings.sh
Add:
#!/bin/bash
systemctl reload wings
Make executable:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-wings.sh

Using Custom Certificates

You can use certificates from any Certificate Authority:
  1. Obtain certificate and key from your CA
  2. Copy to secure location:
sudo mkdir -p /etc/pterodactyl/certs
sudo cp fullchain.pem /etc/pterodactyl/certs/
sudo cp privkey.pem /etc/pterodactyl/certs/
sudo chmod 600 /etc/pterodactyl/certs/privkey.pem
sudo chown root:root /etc/pterodactyl/certs/*
  1. Update configuration:
api:
  ssl:
    enabled: true
    cert: /etc/pterodactyl/certs/fullchain.pem
    key: /etc/pterodactyl/certs/privkey.pem

SFTP Host Keys

Wings automatically generates an ED25519 host key for SFTP on first run.

Key Generation

// sftp/server.go:188-209
func (c *SFTPServer) generateED25519PrivateKey() error {
    _, priv, err := ed25519.GenerateKey(rand.Reader)
    if err != nil {
        return errors.Wrap(err, "sftp: failed to generate ED25519 private key")
    }
    if err := os.MkdirAll(path.Dir(c.PrivateKeyPath()), 0o755); err != nil {
        return errors.Wrap(err, "sftp: could not create internal sftp data directory")
    }
    o, err := os.OpenFile(c.PrivateKeyPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
    if err != nil {
        return errors.WithStack(err)
    }
    defer o.Close()

    b, err := x509.MarshalPKCS8PrivateKey(priv)
    if err != nil {
        return errors.Wrap(err, "sftp: failed to marshal private key into bytes")
    }
    if err := pem.Encode(o, &pem.Block{Type: "PRIVATE KEY", Bytes: b}); err != nil {
        return errors.Wrap(err, "sftp: failed to write ED25519 private key to disk")
    }
    return nil
}

Key Location

The SFTP host key is stored at:
/var/lib/pterodactyl/.sftp/id_ed25519

SFTP Cryptography

Wings uses modern SSH algorithms:
// sftp/server.go:71-84
Config: ssh.Config{
    KeyExchanges: []string{
        "curve25519-sha256", "[email protected]",
        "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
        "diffie-hellman-group14-sha256",
    },
    Ciphers: []string{
        "[email protected]",
        "[email protected]",
        "aes128-ctr", "aes192-ctr", "aes256-ctr",
    },
    MACs: []string{
        "[email protected]", "hmac-sha2-256",
    },
},

Certificate Validation

Testing SSL Configuration

Verify your SSL setup:
# Test HTTPS connection
curl -v https://wings.example.com:8080/api/system

# Check certificate details
openssl s_client -connect wings.example.com:8080 -showcerts

# Test TLS version support
nmap --script ssl-enum-ciphers -p 8080 wings.example.com

Common Issues

Certificate Chain Issues

Problem: Browser shows certificate warnings Solution: Ensure you’re using fullchain.pem, not cert.pem:
ssl:
  cert: /etc/letsencrypt/live/domain/fullchain.pem  # Correct
  # NOT: /etc/letsencrypt/live/domain/cert.pem      # Wrong

Permission Denied

Problem: Wings cannot read certificate files Solution: Check file permissions:
sudo chmod 644 /etc/letsencrypt/live/domain/fullchain.pem
sudo chmod 600 /etc/letsencrypt/live/domain/privkey.pem
sudo chown root:root /etc/letsencrypt/live/domain/*

Mixed Content Warnings

Problem: WebSocket connection fails with SSL Solution: Ensure Panel uses wss:// for WebSocket URLs, not ws://

Security Best Practices

File Permissions

Protect private keys:
# Private key should be readable only by root
sudo chmod 600 /etc/letsencrypt/live/domain/privkey.pem

# Certificate can be world-readable
sudo chmod 644 /etc/letsencrypt/live/domain/fullchain.pem

Certificate Monitoring

Monitor certificate expiration:
# Check expiration date
openssl x509 -in /etc/letsencrypt/live/domain/cert.pem -noout -enddate

# Days until expiration
openssl x509 -in /etc/letsencrypt/live/domain/cert.pem -noout -checkend 604800

HSTS (HTTP Strict Transport Security)

Consider enabling HSTS headers via reverse proxy:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Cipher Suite Selection

Wings uses only secure cipher suites. Never modify DefaultTLSConfig to include weak ciphers like:
  • RC4
  • 3DES
  • CBC-mode ciphers (vulnerable to BEAST/Lucky13)
  • Non-ECDHE ciphers (no forward secrecy)

TLS Version

  • Minimum: TLS 1.2 (required)
  • Recommended: TLS 1.3 (automatic when supported)
  • Never: TLS 1.0 or 1.1 (deprecated and insecure)

Reverse Proxy Configuration

When using a reverse proxy (Nginx, Apache), you can terminate SSL there:

Nginx Example

server {
    listen 443 ssl http2;
    server_name wings.example.com;

    ssl_certificate /etc/letsencrypt/live/wings.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wings.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # WebSocket support
    location /api/servers/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
With a reverse proxy, disable SSL in Wings:
api:
  ssl:
    enabled: false
  trusted_proxies:
    - 127.0.0.1
See Best Practices for more reverse proxy configurations.

Build docs developers (and LLMs) love