Self-Signed SSL/TLS for Posit Team on Rocky Linux
Designed and implemented HTTPS across a co-located Posit Connect, Workbench, and Package Manager deployment using a self-signed certificate with proper SAN support — completed and verified working.
- Rocky Linux
- OpenSSL
- Posit Connect
- Posit Workbench
- Posit Package Manager
- SELinux
Overview
This project delivered HTTPS for a single-server, co-located deployment of Posit Connect, Posit Workbench, and Posit Package Manager on Rocky Linux — an internal environment without a publicly trusted CA or internet-facing port 80/443 available for automated certificate issuance (e.g., Let’s Encrypt).
The solution: a self-signed certificate with a proper Subject Alternative Name (SAN), shared securely across all three services via a dedicated Linux group, with correct file permissions, SELinux compatibility, and client-side trust configuration across macOS, Windows, and Linux.
Outcome: All three Posit products were successfully configured to serve HTTPS, verified via curl and browser checks showing a secure connection with no certificate warnings on client machines.
Prerequisites
- SSH and
sudoaccess to the Linux server - Posit Connect, Workbench, and Package Manager already installed and running over HTTP
- A resolvable hostname for the server (DNS entry or
/etc/hostsentry on client machines)
Phase 1 — Ensure Name Resolution
Clients must be able to resolve <VM_HOSTNAME> to <VM_IP>.
<VM_IP> <VM_HOSTNAME>
Confirm the hostname is set correctly on the server:
sudo hostnamectl set-hostname <VM_HOSTNAME>
Phase 2 — Generate a Self-Signed Certificate with SAN
Modern browsers reject certificates that only specify a Common Name (CN) without a matching SAN. An OpenSSL config file addresses this:
cat <<EOF | sudo tee /etc/ssl/<VM_HOSTNAME>-openssl.cnf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = <VM_HOSTNAME>
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = <VM_HOSTNAME>
EOF
sudo mkdir -p /etc/ssl/private /etc/ssl/certs
sudo openssl req -x509 -nodes -days 825 -newkey rsa:2048 \
-keyout /etc/ssl/private/<VM_HOSTNAME>.key \
-out /etc/ssl/certs/<VM_HOSTNAME>.crt \
-config /etc/ssl/<VM_HOSTNAME>-openssl.cnf \
-extensions v3_req
Phase 3 — Share the Certificate Across Services via a Dedicated Group
Each Posit product runs as its own service user and needs read access to the same private key. Rather than changing ownership per product, a shared group avoids breaking the others:
sudo groupadd -f posit-ssl
sudo usermod -aG posit-ssl rstudio-connect
sudo usermod -aG posit-ssl rstudio-pm
sudo usermod -aG posit-ssl rstudio-server
Permissions on both the key and its parent directory matter — a common pitfall is fixing the file but leaving the directory non-traversable for the group:
sudo chown root:posit-ssl /etc/ssl/private/<VM_HOSTNAME>.key
sudo chmod 640 /etc/ssl/private/<VM_HOSTNAME>.key
sudo chmod 750 /etc/ssl/private
sudo chgrp posit-ssl /etc/ssl/private
sudo chmod 644 /etc/ssl/certs/<VM_HOSTNAME>.crt
Group membership changes require an affected service restart to take effect.
Phase 4 — Configure Posit Connect
/etc/rstudio-connect/rstudio-connect.gcfg:
[Server]
Address = https://<VM_HOSTNAME>:<CONNECT_PORT>
[HTTPS]
Listen = :<CONNECT_PORT>
Certificate = /etc/ssl/certs/<VM_HOSTNAME>.crt
Key = /etc/ssl/private/<VM_HOSTNAME>.key
Permanent = t
sudo systemctl restart rstudio-connect
Phase 5 — Configure Posit Workbench
/etc/rstudio/rserver.conf:
www-port=<WORKBENCH_PORT>
ssl-enabled=1
ssl-certificate=/etc/ssl/certs/<VM_HOSTNAME>.crt
ssl-certificate-key=/etc/ssl/private/<VM_HOSTNAME>.key
ssl-protocols=TLSv1.2 TLSv1.3
sudo rstudio-server restart
If the OS flags the certificate as untrusted at the system level:
sudo cp /etc/ssl/certs/<VM_HOSTNAME>.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
sudo rstudio-server restart
Phase 6 — Configure Posit Package Manager
/etc/rstudio-pm/rstudio-pm.gcfg. Package Manager requires distinct HTTP and HTTPS ports — reusing the same port causes a fatal startup error:
[Server]
Address = https://<VM_HOSTNAME>:<PACKAGE_MANAGER_PORT>
[HTTP]
Listen = :<PACKAGE_MANAGER_HTTP_PORT>
[HTTPS]
Listen = :<PACKAGE_MANAGER_PORT>
Certificate = /etc/ssl/certs/<VM_HOSTNAME>.crt
Key = /etc/ssl/private/<VM_HOSTNAME>.key
sudo rspm check-config
sudo systemctl restart rstudio-pm
Phase 7 — Trust the Certificate on Client Machines
scp <user>@<VM_HOSTNAME>:/etc/ssl/certs/<VM_HOSTNAME>.crt ~/Desktop/
- macOS: Keychain Access → System keychain → drag in cert → set to Always Trust
- Windows: Double-click
.crt→ Install Certificate → Local Machine → Trusted Root Certification Authorities - Linux:
sudo cp <VM_HOSTNAME>.crt /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust extract
Fully quit and reopen the browser after trusting the certificate.
Phase 8 — Verification
curl -vk https://<VM_HOSTNAME>:<CONNECT_PORT>
curl -vk https://<VM_HOSTNAME>:<WORKBENCH_PORT>
curl -vk https://<VM_HOSTNAME>:<PACKAGE_MANAGER_PORT>
Browsing to each URL by hostname (not IP — the SAN only covers the hostname) confirmed a secure connection with no browser warnings across all three services.
Troubleshooting Reference
| Symptom | Likely Cause | Fix |
|---|---|---|
Can't open ".../key" for writing, No such file or directory |
Target directory doesn’t exist | sudo mkdir -p /etc/ssl/private /etc/ssl/certs |
HTTP.Listen and HTTPS.Listen using same value |
Both sections configured on the same port | Assign HTTP a separate port |
open ".../key": permission denied |
Service user lacks read access, or parent directory isn’t traversable | Verify group membership (id <service-user>); confirm 640 root:posit-ssl on the key and 750 root:posit-ssl on the directory |
Self-signed certificate is not in the system CA store |
OS-level trust store missing the cert | sudo cp <cert> /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust extract |
| Browser still shows “Not Secure” | Cert not trusted on the client, browsing by IP, or stale session | Re-check trust settings, use hostname, restart browser |
SELinux denials (ausearch -m avc) |
Policy blocking non-standard cert paths | Relabel with semanage fcontext + restorecon, confirm mode with getenforce |
Scaling Notes
For environments with many client machines, standing up a lightweight internal CA removes the need to manually trust a certificate per client — only the CA’s root certificate needs distributing once. If the server later gains a public domain and open port 80/443, this setup can be swapped for Let’s Encrypt with minimal changes — only the certificate/key source changes; the Posit configuration blocks stay the same.