# ActivityPub with Apache as the reverse proxy

**URL:** <https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286>\
**Category:** Developer help\
**Created:** [December 26, 2025, 10:46pm UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286 "2025-12-26T22:46:15Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![heavyelement](https://avatars.discourse-cdn.com/v4/letter/h/ba9def/32.png) [@heavyelement](https://forum.ghost.org/u/heavyelement)\
**Post date:** [December 26, 2025, 10:46pm UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/1 "2025-12-26T22:46:15Z")

</div>

Hi all!

I’m looking for some help configuring Apache as a reverse proxy for [ap.ghost.org](http://ap.ghost.org)

- **What’s your URL**? [gardinerbryant.com](http://gardinerbryant.com)
- **What version of Ghost are you using?** 6.10.3

I have Ghost installed as a Docker image with Apache as the reverse proxy (for legacy reasons).

Here’s my Apache config:

```c
<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName gardinerbryant.com
    ServerAlias www.gardinerbryant.com

    Alias "/static" "/srv/ghost.gardinerbryant.com/static/"

    ProxyRequests On

    <Directory "/srv/ghost.gardinerbryant.com/static">
        Require all granted
    </Directory>

    ProxyPreserveHost On

    ## Static content
    ProxyPass "/static" !

    SSLProxyEngine On
    ProxyPreserveHost Off
    SSLProxyVerify none
    SSLProxyCheckPeerCN Off
    SSLProxyCheckPeerName Off
    SSLProxyCheckPeerExpire Off

    <Proxy "https://ap.ghost.org:443">

        RequestHeader set Host "ap.ghost.org"
        RequestHeader set "X-Forwarded-Host" "expr=%{SERVER_NAME}"
        RequestHeader set "X-Forwarded-Proto" "https"
        RequestHeader set "X-Forwarded-Port" 443
        RequestHeader set "X-Forwarded-For" "expr=%{REMOTE_ADDR}"
        RequestHeader set "X-Real-IP" "expr=%{REMOTE_ADDR}"

        RequestHeader set "Authorization" "expr=%{HTTP:Authorization}"
        ProxyPassReverseCookieDomain "ap.ghost.org" "gardinerbryant.com"

        Header set "Cache-Control" "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0"
        Header set "Surrogate-Control" "no-transform, no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0"
    </Proxy>

    ProxyPass "/.ghost/activitypub" https://ap.ghost.org:443/.ghost/activitypub
    ProxyPassReverse "/.ghost/activitypub" https://ap.ghost.org:443/.ghost/activitypub
    ProxyPass "/.well-known/webfinger" https://ap.ghost.org:443/.well-known/webfinger
    ProxyPassReverse "/.well-known/webfinger" https://ap.ghost.org:443/.well-known/webfinger
    ProxyPass "/.well-known/nodeinfo" https://ap.ghost.org:443/.well-known/nodeinfo
    ProxyPassReverse "/.well-known/nodeinfo" https://ap.ghost.org:443/.well-known/nodeinfo

    <Proxy "http://127.0.0.1:2368/"> # 2368
        RequestHeader set Host "${SERVER_NAME}"
        RequestHeader set "X-Forwarded-Host" "expr=%{SERVER_NAME}"
        RequestHeader set "X-Forwarded-Proto" "expr=%{REQUEST_SCHEME}"
        RequestHeader set "X-Forwarded-Port" 443
        RequestHeader set "X-Forwarded-For" "expr=%{REMOTE_ADDR}"
        RequestHeader set "Cache-Control" "no-store"

        RequestHeader set "X-Real-IP" "expr=%{REMOTE_ADDR}"
    </Proxy>

    ## Ghost Container
    ProxyPass "/" http://127.0.0.1:2368/
    ProxyPassReverse "/" http://127.0.0.1:2368/

    #RequestHeader set "X-Forwarded-SSL" expr=%{HTTPS}
    # Header set "Access-Control-Allow-Origin" "*"

    ErrorLog ${APACHE_LOG_DIR}/gardinerbryant.com_error.log
    CustomLog ${APACHE_LOG_DIR}/gardinerbryant.com_access.log combined

    RewriteEngine on

    # Some rewrite rules in this file were disabled on your HTTPS site,
    # because they have the potential to create redirection loops.
    RewriteCond %{SERVER_NAME} =www.gardinerbryant.com
    RewriteRule ^ https://gardinerbryant.com%{REQUEST_URI} [END,NE,R=temp]

    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/gardinerbryant.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/gardinerbryant.com/privkey.pem
</VirtualHost>
</IfModule>

```

When I try to access any of the proxied directories directly in my browser, I get this error:

```json
{"error":"Forbidden","code":"SITE_MISSING"}

```

When I try accessing `gardinerbryant.com/ghost/#/activitypub/reader`, I see this as the response for the failed requests:

```json
{"message":"Invalid URL"}

```

Finally, when I go to switch the Network tab off and then on again in the settings, I see this error in Ghost’s logs:

```auto
[2025-12-26 22:40:17] ERROR No webhook secret found - cannot initialise

```

When I searched for the above error message I [found this post](https://forum.ghost.org/t/activitypub-initialization-error-on-self-hosted-ghost-no-webhook-secret-found/60079/13) but it looks like this might be an issue on the remote server side?

I’m not 100% sure what my next steps should be. Any help would be appreciated!

---

<div class="post-metadata">

**Author:** ![markstos](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/markstos/32/18182_2.png) [@markstos](https://forum.ghost.org/u/markstos)\
**Post date:** [January 4, 2026, 7:23pm UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/2 "2026-01-04T19:23:59Z")

</div>

Can you explain more the architecture you are looking to create?

[https://gardinerbryant.com/](https://gardinerbryant.com/)

Appears to to be the domain you want to use for your _Ghost_ instance– that’s what is serving now. This site is behind a reverse proxy provided by Cloudflare currently.

Meanwhile, [ap.ghost.org](http://ap.ghost.org) is an _ActivityPub_ not a Ghost instance.

Since the ActivityPub instance behind Ghost installs is usually not public-facing, why not access [ap.ghost.org](http://ap.ghost.org) instead of trying to proxy traffic to through Apache?

---

<div class="post-metadata">

**Author:** ![heavyelement](https://avatars.discourse-cdn.com/v4/letter/h/ba9def/32.png) [@heavyelement](https://forum.ghost.org/u/heavyelement)\
**Post date:** [January 4, 2026, 9:02pm UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/3 "2026-01-04T21:02:08Z")

</div>

I’m trying to activate Ghost’s ActivityPub features for my self-hosted instance. Every guide I’ve found has described exactly my setup but using Nginx instead of Apache.

I’m trying to do this:

> **[How to Enable ActivityPub on a Self-Hosted Ghost 6 Blog](https://zerotohomelab.cloudboxhub.com/how-to-enable-activitypub-on-a-self-hosted-ghost-6-blog/)**
>
> So you’re self-hosting Ghost and just noticed the new ActivityPub option in version 6, only to find out it doesn’t actually work out-of-the-box for self-hosted setups. 😅
> 
> 
> Yeah… it’s a bit of a pain in the ass.
> 
> 
> 
> 
> 
> 💡 Note: Some hosting setups and...

---

<div class="post-metadata">

**Author:** ![KBExit](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kbexit/32/38359_2.png) [@KBExit](https://forum.ghost.org/u/KBExit)\
**Post date:** [January 4, 2026, 9:25pm UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/4 "2026-01-04T21:25:09Z")

</div>

```auto
<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName gardinerbryant.com
    ServerAlias www.gardinerbryant.com

    # Security: Ensure this is Off (Reverse Proxy mode)
    ProxyRequests Off
    # We keep this Off so we can manually control the Host header for local vs remote
    ProxyPreserveHost Off 
    SSLProxyEngine On

    # Matches Nginx "proxy_ssl_server_name on" logic
    # This ensures Apache uses 'ap.ghost.org' for the SSL handshake SNI
    SSLProxyVerify none
    SSLProxyCheckPeerCN Off
    SSLProxyCheckPeerName Off
    SSLProxyCheckPeerExpire Off

    Alias "/static" "/srv/ghost.gardinerbryant.com/static/"
    <Directory "/srv/ghost.gardinerbryant.com/static">
        Require all granted
    </Directory>

    # Conditional Header Logic: Matches Nginx 'map' logic
    # Only set "nosniff" if the response status is NOT 204
    Header set X-Content-Type-Options "nosniff" "expr=%{REQUEST_STATUS} != 204"

    # 1. ACTIVITYPUB PROXY RULES (ap.ghost.org)
    <LocationMatch "^/(.well-known/(webfinger|nodeinfo)|.ghost/activitypub)">
        # Matches Nginx 'proxy_set_header Host $http_host'
        RequestHeader set Host "gardinerbryant.com"
        
        RequestHeader set X-Forwarded-Proto "https"
        RequestHeader set X-Real-IP "expr=%{REMOTE_ADDR}"
        RequestHeader set X-Forwarded-For "expr=%{REMOTE_ADDR}"

        ProxyPassMatch https://ap.ghost.org
        ProxyPassReverse https://ap.ghost.org
    </LocationMatch>

    # 2. LOCAL GHOST PROXY RULES
    <Location "/">
        ProxyPass "/static" !

        # Matches Nginx 'proxy_set_header Host $http_host'
        RequestHeader set Host "gardinerbryant.com"
        
        RequestHeader set X-Forwarded-Proto "https"
        RequestHeader set X-Real-IP "expr=%{REMOTE_ADDR}"
        RequestHeader set X-Forwarded-For "expr=%{REMOTE_ADDR}"

        ProxyPass http://127.0.0.1:2368/
        ProxyPassReverse http://127.0.0.1:2368/
    </Location>

    # Logging
    ErrorLog ${APACHE_LOG_DIR}/gardinerbryant.com_error.log
    CustomLog ${APACHE_LOG_DIR}/gardinerbryant.com_access.log combined

    # Redirect WWW to Non-WWW
    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^www\.gardinerbryant\.com [NC]
    RewriteRule ^(.*)$ https://gardinerbryant.com$1 [L,R=301]

    # Let's Encrypt Config
    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/gardinerbryant.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/gardinerbryant.com/privkey.pem
</VirtualHost>
</IfModule>

```

Restart and see if that works.

---

<div class="post-metadata">

**Author:** ![heavyelement](https://avatars.discourse-cdn.com/v4/letter/h/ba9def/32.png) [@heavyelement](https://forum.ghost.org/u/heavyelement)\
**Post date:** [January 5, 2026, 12:08am UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/5 "2026-01-05T00:08:48Z")

</div>

Thanks for the suggestions, KBExit!

I’ve tried them and here are the results.

I updated the config file with the one you provided but when I went to reload Apache it failed. `ProxyPass “/static” !` cannot be inside the `<Location “/">` block. (According to the error log the ProxyPass directive inside a Location block cannot have a location defined, even if it’s a negation)

So I moved the `ProxyPass “/static” !` outside the block and it is able to reload. However, content in the static directory is now 404ing!

On the other hand. I tried turning off the ActivityPub feature and then turning it back on. When I do, I get this error in Ghost’s logs:

```auto
[2026-01-04 23:53:11] ERROR Could not get webhook secret for ActivityPub FetchError: invalid json response body at https://gardinerbryant.com/.ghost/activitypub/v1/site/ reason: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
[2026-01-04 23:53:11] ERROR No webhook secret found - cannot initialise

```

I was never seeing the JSON response body error before.

Curious, I visited `https://gardinerbryant.com/.ghost/activitypub/v1/site/` and it’s now serving a standard Ghost 404 page. So the proxy defined in the `<LocationMatch>` block seems to not be working.

However, the reverse proxy defined in the `<Location>` block (meaning my Ghost site) _is working just fine_.

It should be noted that I also tried escaping the forward slashes in the regex `<LocationMatch “^/(.well-known/(webfinger|nodeinfo)|.ghost/activitypub)”>` and that still isn’t working for the [ap.ghost.org](http://ap.ghost.org) proxy. I’ve also tried explicitly defining the port for the Proxy.

I’ve reverted my config back to the previously shared file.

---

<div class="post-metadata">

**Author:** ![KBExit](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kbexit/32/38359_2.png) [@KBExit](https://forum.ghost.org/u/KBExit)\
**Post date:** [January 5, 2026, 12:15am UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/6 "2026-01-05T00:15:51Z")

</div>

I’m sorry it didn’t work. I don’t have a test environment for Apache.  
Can I ask why you’re not using the proposed Caddy Method in the Docker Compose?

---

<div class="post-metadata">

**Author:** ![heavyelement](https://avatars.discourse-cdn.com/v4/letter/h/ba9def/32.png) [@heavyelement](https://forum.ghost.org/u/heavyelement)\
**Post date:** [January 5, 2026, 12:17am UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/7 "2026-01-05T00:17:35Z")

</div>

Also, now that I’ve reverted to my previous config, I visited `https://gardinerbryant.com/.ghost/activitypub/v1/site/` again from my browser (by directly entering it in my address bar. I get this response:

```json
{"error":"Forbidden","code":"ROLE_MISSING"}

```

This, to me, says that my proxy config is _more or less_ correct.

Out of curiosity, I also tried turning off the ActivityPub setting within Ghost and then turning it back on. I got the following error:

```auto
[2026-01-05 00:12:12] INFO "GET /ghost/api/admin/integrations/?include=api_keys%2Cwebhooks&limit=50" 200 38ms
[2026-01-05 00:12:12] INFO "GET /ghost/api/admin/settings/?group=site%2Ctheme%2Cprivate%2Cmembers%2Cportal%2Cnewsletter%2Cemail%2Clabs%2Cslack%2Cunsplash%2Cviews%2Cfirstpromoter%2Ceditor%2Ccomments%2Canalytics%2Cannouncement%2Cpintura%2Cdonations%2Crecommendations%2Csecurity%2Csocial_web%2Cexplore" 200 18ms
[2026-01-05 00:12:12] ERROR No webhook secret found - cannot initialise

```

As you can see, there’s no error about invalid JSON.

My suspicion is that there’s either a misconfiguration in Ghost on my end. Is there some kind of JWT secret that needs to be defined in my config file?

As far as Caddy goes, I don’t know what that is.

EDIT: I checked the install guide and I guess when I read the info about Caddy I thought it was for running Ghost admin on a separate domain. I’ll try implementing caddy into my compose and get back to you.

---

<div class="post-metadata">

**Author:** ![KBExit](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kbexit/32/38359_2.png) [@KBExit](https://forum.ghost.org/u/KBExit)\
**Post date:** [January 5, 2026, 12:21am UTC](https://forum.ghost.org/t/activitypub-with-apache-as-the-reverse-proxy/61286/8 "2026-01-05T00:21:58Z")

</div>

That’s the official Reverse Proxy that Ghost 6 ships with. If you’re installing this other than Docker, you’re going to find yourself in a bind when 7 comes out and CLI is discontinued.

> **[How To Install Ghost With Docker (preview) - Ghost Developer Docs](https://docs.ghost.org/install/docker)**
>
> Preview our new batteries-included tools for self-hosting Ghost using Docker Compose.
