HTTPS and certificates
Service workers, the clipboard API, camera and microphone access and Secure cookies all
expect a secure context. Local development is easier when it looks like production, so
Vhostly can put every site on HTTPS — with a padlock, not a warning.
The two halves
Getting a green padlock locally needs two separate things, and confusing them is where most of the frustration comes from.
A certificate for the site. Issued per hostname, stored on disk, referenced by the
:443 vhost.
A trusted authority that signed it. mkcert generates a local certificate authority
once. Until macOS is told to trust that authority, every certificate it signs is valid but
rejected — ERR_CERT_AUTHORITY_INVALID. The certificate is fine; nobody vouches for the
signer.
The SSL panel shows both, plus whether mod_ssl is loaded and whether Apache is
listening on 443.
Enabling HTTPS for a site
Tick Enable HTTPS when creating a site, or use the SSL panel afterwards. There is nothing to prepare first. Vhostly runs the whole chain and reports which steps actually changed something:
- Installs mkcert with Homebrew, if it is not installed yet.
- Issues a certificate for the hostname. On the very first run, mkcert also creates its local authority.
- Trusts that authority, if macOS does not trust it yet. macOS asks for your password once; every site after that skips this step.
- Enables
mod_socache_shmcb— without a session cache provider,mod_sslrefuses to start. - Enables
mod_ssl. - Adds
Listen 443next toListen 80. - Includes
httpd-vhosts-ssl.conffromhttpd.conf. - Writes a
<VirtualHost *:443>block mirroring the site's document root or proxy port.
Then it runs apachectl -t. If the result does not parse, nothing is restarted and you
get the error — a bad certificate path cannot take your running Apache down.
If mkcert cannot be installed, or you cancel the password prompt, the site still gets HTTPS. The message tells you why browsers will warn, and nothing else is rolled back.
The :443 blocks live in their own file, separate from the :80 ones. That keeps the
plain-HTTP parser and the backup logic from ever having to reason about TLS.
Enable HTTPS or Force HTTPS
The two options do different things.
| Option | https:// | http:// | Badge on the row |
|---|---|---|---|
| Enable HTTPS | Served | Also served | SSL |
| Force HTTPS | Served | 301 to https:// | SSL and HTTPS ONLY |
Force HTTPS always includes HTTPS, so a forced site shows both badges.
Forcing HTTPS
A certificate makes https:// work. It does not stop http:// from answering. Turn on
Force HTTPS for a site (the padlock button on its row, or the checkbox in the form) and
its :80 vhost answers every request with a 301 to https:// instead.
If the site has no HTTPS yet, turning it on sets that up first. If HTTPS cannot be set up, the redirect is removed again, because redirecting to a port Apache does not serve is worse than plain HTTP. Removing a site's certificate also removes its redirect.
The redirect goes directly under ServerName, ahead of any ProxyPass, so it applies to
proxy sites too:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
mkcert, or a fallback
With mkcert, which Vhostly installs the first time you enable HTTPS, the certificate is signed by your local authority and browsers trust it once that authority is trusted.
Without mkcert, because Homebrew could not install it, Vhostly falls back to a self-signed OpenSSL certificate, valid for 825 days. It encrypts correctly and every browser warns about it, every time. It is a fallback, not a plan.
Either way the certificate covers the hostname and its wildcard — myapp.test and
*.myapp.test — so subdomains are already included.
Files land in /opt/homebrew/etc/httpd/certs/, as myapp.test.pem and
myapp.test-key.pem.
Trusting the local authority
This is the step that removes browser warnings. Enabling HTTPS on a site does it for you the first time. If you cancelled that prompt, Trust local CA on the SSL panel runs the same step again. macOS shows its own password prompt; after it, reload the page and you should see a padlock.
Vhostly adds the authority to your login keychain, not the system keychain:
security add-trusted-cert -r trustRoot \
-k ~/Library/Keychains/login.keychain-db "$(mkcert -CAROOT)/rootCA.pem"
That is deliberate, and it is not what mkcert -install does. mkcert -install targets
the system keychain, and macOS insists on drawing its own authorization dialog for that
store — a dialog that cannot be drawn when the command is running as root with no GUI
session attached. The result is a failure that says "no user interaction was possible"
even when the password was correct. Writing to the login keychain avoids root entirely, and
macOS trust evaluation — which is what Chrome and Safari both use — honours per-user trust
settings.
If it fails anyway, the app shows you the system-wide command to run yourself:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain "$(mkcert -CAROOT)/rootCA.pem"
Vhostly checks trust with security verify-cert, which reflects the trust setting rather
than merely finding the certificate in a keychain. That distinction is exactly why a
certificate can look installed and still be refused.
Other clients
Safari and Chrome read the macOS keychain, so they are covered.
Firefox keeps its own trust store. Set security.enterprise_roots.enabled to true in
about:config, or import rootCA.pem by hand.
Node does not read the keychain either. Point it at the root:
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
curl uses the system store and works once the authority is trusted.
A certificate follows the hostname
Rename a site and its old certificate no longer matches. Re-issue from the SSL panel; the new one is written under the new name.
Turning HTTPS off
Disabling HTTPS for a site removes its :443 block and deletes its certificate and key.
This is not a pause — turning HTTPS back on issues a fresh certificate. mod_ssl,
Listen 443 and the other sites are left exactly as they were.
mod_ssl itself can be toggled separately on the SSL panel, which affects every HTTPS site
at once.