Short answer: port 80 is the default port for plain HTTP and port 443 the default for HTTPS, both over TCP. Since HTTP/3, port 443 is also used over UDP, for QUIC. A public web server normally opens TCP 80 (to redirect visitors and pass certificate checks), TCP 443 for the site itself and, if it serves HTTP/3, UDP 443.
| Port | Carries | Notes |
|---|---|---|
| 80/TCP | HTTP, unencrypted | Keep open for the redirect to HTTPS and Let's Encrypt HTTP-01 checks |
| 443/TCP | HTTPS: HTTP/1.1 and HTTP/2 inside TLS | The site itself |
| 443/UDP | HTTP/3 over QUIC | Optional; browsers fall back to TCP 443 when it is blocked |
| 8080/TCP | HTTP alternate (IANA name http-alt) | App servers and proxies; browsers never try it on their own |
| 8443/TCP | HTTPS alternate, by convention | Admin panels, Tomcat; not a browser default either |
A URL without a port uses the scheme’s default: http://example.com means TCP 80, https://example.com means 443. Anything else has to be written out, as in https://example.com:8443/.
Is port 443 TCP or UDP?
Both, for different HTTP versions. HTTP/1.1 and HTTP/2 run over TLS on TCP 443. HTTP/3 (RFC 9114) runs over QUIC (RFC 9000), which is built on UDP, normally on UDP 443. A browser does not try UDP blindly: the first response over TCP advertises HTTP/3 in an Alt-Svc header (or the domain publishes an HTTPS record in DNS), and only then does the browser switch. If UDP is blocked anywhere on the way, it keeps using TCP, so a closed UDP 443 costs speed, never reachability.
You can see the advertisement with curl:
curl -sI https://one.one.one.oneHTTP/2 200
server: cloudflare
alt-svc: h3=":443"; ma=86400h3=":443" means “HTTP/3 is available on UDP port 443 of this host”, and ma=86400 lets the browser remember that for a day. To see which version a browser actually used, open the developer tools, Network tab, and look at the Protocol column (in Chrome, right-click the column headers to show it): h3 means QUIC on UDP 443, h2 means TLS on TCP 443. A curl built with HTTP/3 can test UDP 443 on your own domain directly. Debian 13’s curl is; the curl in Debian 12 and Ubuntu 24.04 is not, and curl -V shows it: look for HTTP3 in the Features line.
curl --http3-only -sI https://example.com--http3-only forbids the fallback to TCP, so an HTTP/3 200 proves that UDP 443 is open all the way. A timeout while the same URL answers over TCP means something on the path drops UDP 443, or the server does not listen on it.
What is the difference between port 80 and 443?
The HTTP inside is the same. On port 443 the connection starts with a TLS handshake: the client names the site it wants (SNI), the server presents a certificate for that name, and everything after that is encrypted. On port 80 the requests, cookies and form data cross the network as plain text that every router on the path can read and change. That is why port 80 today should only answer with a redirect to HTTPS.
Mixing them up produces two very recognisable errors. Sending plain HTTP to a TLS port, and TLS to a plain-HTTP port:
curl -s http://nginx.org:443/ | grep -o '<title>.*</title>'
curl -sS -o /dev/null https://nginx.org:80/<title>400 The plain HTTP request was sent to HTTPS port</title>
curl: (35) OpenSSL/3.0.22: error:0A00010B:SSL routines::wrong version number| You see | What happened | Fix |
|---|---|---|
400 The plain HTTP request was sent to HTTPS port | An http:// URL or a proxy_pass to http:// points at a TLS port | Use https://, or proxy to the backend's plain-HTTP port |
SSL routines::wrong version number | An https:// URL points at a port that speaks plain HTTP (80, or an app on 3000) | Use http:// for that port, or put TLS in front of it |
| ERR_SSL_PROTOCOL_ERROR | The browser's version of the same mistake, or a broken TLS setup | Check the port and the server block's listen ... ssl line |
Why keep port 80 open at all?
Closing 80 looks tidy but breaks two things:
- Visitors who arrive on http://. Old links, hand-typed addresses and some clients and bots still start on port 80. With 80 closed they get a connection error instead of a redirect. After their first HTTPS visit, an HSTS header makes browsers skip port 80 by themselves.
- Certificate renewal. Let’s Encrypt’s HTTP-01 challenge, which certbot uses by default, connects to TCP port 80 only. It follows up to 10 redirects to http or https on ports 80 or 443, without checking the certificate on the way, so a plain 301 to HTTPS does not get in its way. A closed port 80 does.
If you really want port 80 closed, issue certificates with the DNS-01 challenge (no open port needed; certbot has DNS plugins for the big providers) or TLS-ALPN-01, which works on port 443 alone but needs a client such as Caddy or Traefik, since certbot does not implement it. Either way, http:// visitors then see an error.
How to see what is listening on ports 80 and 443
sudo ss -tlnp '( sport = :80 or sport = :443 )' # TCP
sudo ss -ulnp 'sport = :443' # UDP, for HTTP/3State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1123,fd=6),("nginx",pid=1121,fd=6))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1123,fd=8),("nginx",pid=1121,fd=8))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=1123,fd=7),("nginx",pid=1121,fd=7))
LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=1123,fd=9),("nginx",pid=1121,fd=9))With HTTP/3 enabled, the UDP command adds UNCONN lines on port 443 owned by nginx. 0.0.0.0 and [::] mean every IPv4 and IPv6 address, so the port is reachable from outside if the firewall allows it. 127.0.0.1:443 would mean local connections only. No output means nothing listens and the browser gets ERR_CONNECTION_REFUSED. The same question with lsof, which some find easier to read (on a minimal Debian, sudo apt install lsof first):
sudo lsof -nP -iTCP:443 -sTCP:LISTENHow to open ports 80 and 443 in the firewall
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp # only if you serve HTTP/3
sudo ufw status verboseThe shortcut sudo ufw allow 'Nginx Full' opens 80 and 443 over TCP only. Debian 13’s nginx package adds an Nginx QUIC profile that also opens UDP 443; Debian 12 and Ubuntu 24.04 have no such profile, so add the UDP rule by hand. The rest of the setup, with rate limits and source restrictions, is in the UFW firewall guide.
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --permanent --add-port=443/udp # only for HTTP/3
sudo firewall-cmd --reloadIf you write nftables rules by hand, these are the lines for the input chain:
tcp dport { 80, 443 } accept
udp dport 443 acceptA provider-side firewall or security group in front of the server needs the same ports. If the server answers locally but not from outside, that is the first place to look.
How to test ports 80 and 443 from outside
Run these from your own computer, not from the server: a test on the server itself goes over the loopback interface, which firewalls let through, so it proves little. Replace example.com with your domain.
curl -sI http://example.com
curl -sI https://example.comopenssl s_client -connect example.com:443 -servername example.com -brief </dev/nullHTTP/1.1 301 Moved Permanently
Location: https://vmheaven.io/
HTTP/2 200
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = vmheaven.io
Verification: OKTest-NetConnection example.com -Port 443Port 80 should answer with a 301 or 308 to the https:// address, port 443 with your page. Verification: OK confirms the certificate chain, and on Windows TcpTestSucceeded : True confirms the port is reachable. OpenSSL 3 sends the host name from -connect as SNI on its own; -servername matters when you connect to an IP address, for example to test a new server before DNS points at it: -connect 203.0.113.10:443 -servername example.com.
Without a terminal: the open port checker tests whether TCP 80 or 443 on your server’s IP address accepts connections from our server in Amsterdam, the SSL checker shows the certificate, chain and TLS versions on port 443, and the HTTP header checker follows the redirect from port 80 hop by hop and lists the response headers, alt-svc included. None of them can test UDP 443; for HTTP/3, use the browser’s Protocol column or curl --http3-only as shown above.
Redirect port 80 to 443 in nginx
One server block on port 80 that only redirects, one on 443 that serves the site. Replace example.com with your domain and the certificate paths with yours:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# lets certbot --webroot work even while HTTPS is broken
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
}sudo nginx -t && sudo systemctl reload nginxnginx -t checks the configuration and the certificate files before the reload, so a typo never takes the site down. For HTTP/2 on 443, add http2 on; to the HTTPS block on nginx 1.25.1 and later (Debian 13); on Debian 12 and Ubuntu 24.04 write listen 443 ssl http2; instead. Anyone who connects to the server’s bare IP, which every scanner does, is answered by the first server block for that port unless you define a default. These two blocks close that gap: port 80 drops the connection (nginx’s special code 444) and port 443 refuses the TLS handshake instead of showing one of your certificates:
server {
listen 80 default_server;
listen [::]:80 default_server;
return 444;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_reject_handshake on;
}ssl_reject_handshake needs nginx 1.19.4 or newer: Debian 12 and 13 and Ubuntu 24.04 have it, Debian 11 and Ubuntu 22.04 ship 1.18 and do not. Remove the default_server flag from the stock sites-enabled/default file first, or nginx -t stops with a duplicate default server for 0.0.0.0:80.
Serve HTTP/3 on UDP 443 with nginx
nginx supports QUIC from version 1.25.0, if built with the http_v3 module. Debian 13 ships 1.26 with it; Debian 12 (1.22) and Ubuntu 24.04 (1.24) are too old, and the nginx.org packages are the usual way there. nginx -V 2>&1 | grep -o http_v3_module tells you whether your build has it. Add to the HTTPS server block:
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400' always;Keep the existing listen 443 ssl; lines: the first response still comes over TCP and carries the Alt-Svc header. If a location block has add_header lines of its own, nginx drops the server-level ones there, so repeat the header in it. Then open UDP 443 in the firewall as shown above. reuseport may appear only once per address and port across all server blocks: give it to one site and use plain listen 443 quic; in the others, or nginx -t stops with duplicate listen options for 0.0.0.0:443.
Why can’t my app bind to port 80 or 443?
On Linux, ports below 1024 are privileged: only root, or a process with the CAP_NET_BIND_SERVICE capability, may bind them. The boundary is the sysctl net.ipv4.ip_unprivileged_port_start, 1024 by default. An app started as a normal user fails with listen EACCES: permission denied 0.0.0.0:443 (Node.js) or [Errno 13] Permission denied (Python). nginx and Apache do not hit this because their master process starts as root, binds the ports and then runs its workers as an unprivileged user (www-data on Debian and Ubuntu).
The cleanest fix is not to let the app bind 443 at all: keep it on 127.0.0.1:3000 and let nginx terminate TLS in front of it. If the app must own the port, give its systemd service exactly that one capability instead of running it as root. sudo systemctl edit YOUR_APP.service opens a drop-in; replace YOUR_APP and YOUR_APP_USER with your service and the user it runs as:
[Service]
User=YOUR_APP_USER
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICEsudo systemctl restart YOUR_APP.serviceA different error, bind() to 0.0.0.0:80 failed (98: Address already in use), means the permission is fine but another process already holds the port, typically Apache and nginx installed side by side, or a container publishing 80. The address already in use guide finds and stops it. And if you need SSH, a VPN and HTTPS all on 443 because a network only lets 443 out, a multiplexer such as sslh, or nginx’s stream module with ssl_preread routing by SNI, can share the port; that is an exception, not a default.
Ports 80 and 443 behind Cloudflare
A proxied (orange-cloud) Cloudflare record accepts web traffic only on a fixed list of ports, per Cloudflare’s network ports reference. Anything else needs a DNS-only record or Cloudflare Spectrum.
| Traffic | Ports | Cached? |
|---|---|---|
| HTTP | 80, 8080, 8880, 2052, 2082, 2086, 2095 | 80 and 8080 yes; the others not by default |
| HTTPS | 443, 2053, 2083, 2087, 2096, 8443 | 443 yes; the others not by default |
- Your origin does not need UDP 443. Cloudflare speaks HTTP/3 to visitors but connects to your server over TCP; HTTP/3 to the origin is not supported.
- The SSL mode decides the origin port. Flexible connects to your server over plain HTTP on port 80. If that server redirects HTTP to HTTPS, every request loops back to Cloudflare and the browser gives up with too many redirects. Full and Full (strict) use the visitor’s scheme: HTTPS requests reach the origin on 443, so it must serve HTTPS there (Full accepts any certificate, Full (strict) only a valid one), and plain-HTTP requests still reach port 80 unless Cloudflare’s Always Use HTTPS redirects them at the edge.
- Allow Cloudflare in your firewall. Its address ranges are published at cloudflare.com/ips. If the origin firewall drops Cloudflare on 80 or 443, visitors get Cloudflare error 522, a connection timeout.
Every Ubuntu VPS at VMHeaven comes with its own IPv4 address and full root, so ports 80 and 443 are yours to open; UFW ships switched off, and the VNC console in the panel gets you back in if a firewall rule goes wrong. If you use the packet rules in the Standard KVM panel, open 80 and 443 there as well.
Frequently asked
Is port 443 TCP or UDP?
Both. HTTPS with HTTP/1.1 and HTTP/2 uses TCP 443, while HTTP/3 runs over QUIC on UDP 443. Browsers only switch to UDP after the server advertises HTTP/3, and they fall back to TCP if UDP is blocked.
Do I need to keep port 80 open if my site only uses HTTPS?
Usually yes. Port 80 redirects visitors who arrive on http:// links, and Let's Encrypt's HTTP-01 challenge connects on port 80 only. Close it only if you issue certificates with the DNS-01 challenge and accept that http:// visitors get an error.
Why does my app get 'permission denied' when binding port 80 or 443?
On Linux, ports below 1024 need root or the CAP_NET_BIND_SERVICE capability. Run the app on a high port such as 3000 behind nginx, or give its systemd service AmbientCapabilities=CAP_NET_BIND_SERVICE instead of running it as root.