Thanks - I can confirm the authenticated feed itself is now being generated properly on my side.
The remaining issue seems to be client compatibility: neither Google Calendar nor Outlook appears happy subscribing directly to an authenticated ICS feed in this form, so for now I’m planning to work around it by putting a host-level Nginx reverse proxy in front of my single-container Discourse install and serving a plain .ics file there instead.
Because I’m on the standard single-container setup, I think this means moving ports 80/443 off the container, making Discourse listen on an internal high port, then having host Nginx proxy the forum and also serve a static calendar feed path.
Roughly, the commands I’m expecting to use are:
# 1. Install host nginx + certbot
sudo apt update
sudo apt install -y nginx snapd
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -sf /snap/bin/certbot /usr/bin/certbot
# 2. Stop the Discourse container so ports 80/443 can be freed
cd /var/discourse
sudo ./launcher stop app
# 3. Edit the container config so Discourse no longer binds directly to 80/443
sudo nano /var/discourse/containers/app.yml
Then in app.yml change the expose section from:
expose:
- "80:80"
- "443:443"
to something like:
expose:
- "127.0.0.1:8080:80"
and, if present, remove the container SSL / Let’s Encrypt templates so TLS is terminated on the host reverse proxy instead.
Then rebuild:
cd /var/discourse
sudo ./launcher rebuild app
Then create a host-level Nginx site such as:
sudo nano /etc/nginx/sites-available/discourse
with something along the lines of:
server {
listen 80;
listen [::]:80;
server_name forum.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location /private-calendar/ethan.ics {
alias /var/www/private-calendar/ethan.ics;
default_type text/calendar;
add_header Content-Type "text/calendar; charset=utf-8";
}
}
Enable it and reload:
sudo mkdir -p /var/www/private-calendar
sudo mkdir -p /var/www/certbot
sudo ln -s /etc/nginx/sites-available/discourse /etc/nginx/sites-enabled/discourse
sudo nginx -t
sudo systemctl reload nginx
Then obtain a Let’s Encrypt certificate with Certbot and let it update the Nginx config:
sudo certbot --nginx -d forum.example.com
sudo nginx -t
sudo systemctl reload nginx
After that, the Nginx config will typically include an HTTPS server block as well, e.g.:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name forum.example.com;
ssl_certificate /etc/letsencrypt/live/forum.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/forum.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
location /private-calendar/ethan.ics {
alias /var/www/private-calendar/ethan.ics;
default_type text/calendar;
add_header Content-Type "text/calendar; charset=utf-8";
}
}
At that point I can use a script/timer on the host to fetch the authenticated Discourse ICS and write out:
/var/www/private-calendar/ethan.ics
which Google Calendar / Outlook can subscribe to as a normal public ICS URL.
So from my perspective the Discourse side looks solved now; the practical remaining gap is mostly that major calendar clients don’t handle authenticated ICS feeds particularly well, which is why I’m falling back to a public proxy/static-file approach for now.
I’m also assuming Certbot is the simplest route here, since it can manage Let’s Encrypt issuance/renewal directly against host Nginx. I could also use acme.sh, but my impression is that it would be more of a manual choice here rather than the most straightforward path for this specific setup.