What's with ActivityPub + WordPress + Cloudflare not working? 1

I’ve been on a journey to get WPwatercooler.com to be on the fediverse and it hasn’t been as easy as I was thinking it was going to be. Folks that run a website and want to use Mastodon to build community around it have a few options for being presented on the Fediverse. This post isn’t going to help you solve this yet but I hope we can work out what needs to be done in order to do so.

Some new developments

Since writing this post, others have discovered solutions to the issues I discuss below. One such solution is provided by Dustin Rue in a blog post Caching WordPress author pages when using ActivityPub, which was extremely helpful in attempting to resolve the Cloudflare problem. However, it should be noted that implementing this solution requires access to the Nginx configuration file. If you are hosting with SpinupWP, which provides such access, Dustin’s post may be useful in resolving this issue.

  1. Make an account on an existing Mastodon instance, share your links to your content there
  2. Build your own Mastodon instance and make an account there and share your content
  3. Use ActivityPub and allow people to follow your website on any Fediverse property

There is most likely more but for the most part its those 3 that you can select from.

I started out with WPwatercooler on Mastodon.online which can be found at https://mastodon.online/@wpwatercooler this works fine and people can follow it but I wanted to see if I could take it a step further and add WPwatercooler.com to the Fediverse directly and this is where ActivityPub comes in.

I installed these three plugins so that I could offer up ActivityPub via WebFinger and people on Mastodon will be able to find the website on there. I created 2 accounts on WPwatercooler.com one for each show wpwatercooler and devbranch and then I did a search for @wpwatercooler and @devbranch on my Mastodon instance and was able to find them.

The Problem

The thing I and other folks in our community ran into was that we could follow the accounts and if we tried a bunch sometimes it should register. Seems to be a caching issues I think. Let’s check and see.

WebFinger

In part WebFinger is a protocol that allows for discovery of information about people and things identified by a URI. Information about a person might be discovered via an acct: URI, for example, which is a URI that looks like an email address.

Both of these accounts can be found by going to https://wpwatercooler.com/author/devbranch/ and https://wpwatercooler.com/author/wpwatercooler/ as you can see both of these are authors on the WPwatercooler website like I spoke about before. When someone tries and searches for @wpwatercooler on Mastodon it accesses https://wpwatercooler.com/.well-known/webfinger?resource=acct:wpwatercooler@wpwatercooler.com and as you can see from the url it looks at the domain name in the .well-known/webfinder and looks for the resource with the account name of wpwatercooler@wpwatercooler.com

Webfinger offers up a json output that looks like the following:

{
“subject”: “acct:wpwatercooler@wpwatercooler.com”,
“aliases”: [
“acct:wpwatercooler@wpwatercooler.com”,
“https://wpwatercooler.com/author/wpwatercooler/”,
“mailto:jason+wpwatercooler@wpwatercooler.com”
],
“links”: [
{
“rel”: “http://webfinger.net/rel/profile-page”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/”,
“type”: “text/html”
},
{
“rel”: “http://webfinger.net/rel/avatar”,
“href”: “https://wpwatercooler.com/wp-content/uploads/2015/06/cropped-WPwatercooler-Avatar-2023-2-96x96.png”
},
{
“rel”: “http://webfinger.net/rel/profile-page”,
“href”: “https://www.wpwatercooler.com”,
“type”: “text/html”
},
{
“rel”: “self”,
“type”: “application/activity+json”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/”
},
{
“rel”: “http://nodeinfo.diaspora.software/ns/schema/2.0”,
“href”: “https://wpwatercooler.com/wp-json/nodeinfo/2.0”
},
{
“rel”: “http://nodeinfo.diaspora.software/ns/schema/1.1”,
“href”: “https://wpwatercooler.com/wp-json/nodeinfo/1.1”
},
{
“rel”: “http://nodeinfo.diaspora.software/ns/schema/1.0”,
“href”: “https://wpwatercooler.com/wp-json/nodeinfo/1.0”
},
{
“rel”: “https://feneas.org/ns/serviceinfo”,
“type”: “application/ld+json”,
“href”: “https://wpwatercooler.com/wp-json/serviceinfo/1.0”,
“properties”: {
“https://feneas.org/ns/serviceinfo#software.name”: “WPwatercooler”
}
},
{
“rel”: “http://schemas.google.com/g/2010#updates-from”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/feed/ostatus/”,
“type”: “application/atom+xml”
},
{
“rel”: “http://ostatus.org/schema/1.0/subscribe”,
“template”: “https://wpwatercooler.com/?profile={uri}”
},
{
“rel”: “feed”,
“type”: “application/stream+json”,
“title”: “Activity-Streams 1.0 Feed”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/feed/as1/”
},
{
“rel”: “feed”,
“type”: “application/activity+json”,
“title”: “Activity-Streams 2.0 Feed”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/feed/as2/”
}
]
}

the section we’re interested in is

{
“rel”: “self”,
“type”: “application/activity+json”,
“href”: “https://wpwatercooler.com/author/wpwatercooler/”
},

This section has the author url and you can see that its using the type of application/activity+json instead of something like text/html so when it queries that url it passes along in the header Accept: application/activity+json and it should return it in that format.

Running the following command

$ url -i -H “Accept: application/activity+json” “https://wpwatercooler.com/author/wpwatercooler”

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 —:—:— —:—:— —:—:—     0HTTP/2 200
date: Tue, 17 Jan 2023 17:42:34 GMT
content-type: text/html; charset=UTF-8
last-modified: Tue, 17 Jan 2023 16:11:53 GMT
cache-control: max-age=0
expires: Tue, 17 Jan 2023 16:40:07 GMT
vary: Accept-Encoding
age: 3746
x-cache: HIT
cf-cache-status: DYNAMIC
server-timing: cf-q-config;dur=9.0000003183377e-06
report-to: {“endpoints”:[{“url”:“https:\/\/a.nel.cloudflare.com\/report\/v3?s=hTOA5wCRdqy4Lkoey48CmI2UIbYym8HWAvtsssC4lRsXgli7%2BchgWImTY3ol%2FdS8gTcodIYZQ6cbSDMVZ1co5brwCJxEpkIdqJ%2FCEjwKcxOWhomHgfWISN9qzmkfkMd7tNo8fQORQ%3D%3D”}],“group”:“cf-nel”,“max_age”:604800}
nel: {“success_fraction”:0,“report_to”:“cf-nel”,“max_age”:604800}
server: cloudflare
cf-ray: 78b0e3631e862ea3-LAX
alt-svc: h3=“:443”; ma=86400, h3-29=“:443”; ma=86400

Cloudflare returns back text/html and outputs the author page and not the json we wanted. Talking with a few folks on Mastodon we found that people are having this issue with using Cloudflare and there isn’t anything we can do to resolve it sadly. I tried excluding the caching for the specific paths /author/(.*) and other such tricks and it keeps changing the content-type to text/html and not application/activity+json as requested.

Looking online I’ve found a bunch of people using Cloudflare + ActivityPub running into this issue and other technologies that also use Accept: application/activity+json and finding that it only returns text/html. Bummer.

The code that makes all that work can be found in the WordPress ActivityPub plugin class-activity-pub.php

/**
	 * Return a AS2 JSON version of an author, post or page.
	 *
	 * @param  string $template The path to the template object.
	 *
	 * @return string The new path to the JSON template.
	 */
	public static function render_json_template( $template ) {
		if ( ! \is_author() && ! \is_singular() && ! \is_home() ) {
			return $template;
		}

		// check if user can publish posts
		if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) {
			return $template;
		}

		if ( \is_author() ) {
			$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
		} elseif ( \is_singular() ) {
			$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
		} elseif ( \is_home() ) {
			$json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/blog-json.php';
		}

		global $wp_query;

		if ( isset( $wp_query->query_vars['activitypub'] ) ) {
			return $json_template;
		}

		if ( ! isset( $_SERVER['HTTP_ACCEPT'] ) ) {
			return $template;
		}

		$accept_header = $_SERVER['HTTP_ACCEPT'];

		if (
			\stristr( $accept_header, 'application/activity+json' ) ||
			\stristr( $accept_header, 'application/ld+json' )
		) {
			return $json_template;
		}

		// Accept header as an array.
		$accept = \explode( ',', \trim( $accept_header ) );

		if (
			\in_array( 'application/ld+json; profile=“https://www.w3.org/ns/activitystreams”', $accept, true ) ||
			\in_array( 'application/activity+json', $accept, true ) ||
			\in_array( 'application/ld+json', $accept, true ) ||
			\in_array( 'application/json', $accept, true )
		) {
			return $json_template;
		}

		return $template;
	}

Can this be done?

After discussing this with @tomfinley and @tim on mastodon in this tread https://simian.rodeo/@jasontucker/109701436056062907 we found that there is an endpoint that will return json https://wpwatercooler.com/author/wpwatercooler/activitypub

I’m wondering if instead we can force Webfinger to return that url with /activitypub at the end instead of expecting Cloudflare to return a non text/html content type. You can see this JSON output here https://wpwatercooler.com/author/wpwatercooler/activitypub/

“id”: “https://wpwatercooler.com/author/wpwatercooler/”,
“type”: “Person”,
“name”: “WPwatercooler Show”,
“summary”: “The world’s most influential WordPress podcast brought to you by Jason Tucker, Sé Reed & Jason Cosper”,
“preferredUsername”: “wpwatercooler”,
“url”: “https://wpwatercooler.com/author/wpwatercooler/”,
“icon”: {
“type”: “Image”,
“url”: “https://wpwatercooler.com/wp-content/uploads/2015/06/cropped-WPwatercooler-Avatar-2023-2-120x120.png”
},
“inbox”: “https://wpwatercooler.com/wp-json/activitypub/1.0/users/6260/inbox”,
“outbox”: “https://wpwatercooler.com/wp-json/activitypub/1.0/users/6260/outbox”,
“followers”: “https://wpwatercooler.com/wp-json/activitypub/1.0/users/6260/followers”,
“following”: “https://wpwatercooler.com/wp-json/activitypub/1.0/users/6260/following”,
“manuallyApprovesFollowers”: false,
“publicKey”: {
“id”: “https://wpwatercooler.com/author/wpwatercooler/#main-key”,
“owner”: “https://wpwatercooler.com/author/wpwatercooler/”,

What’s involved to make this work on Cloudflare? I’d love to hear in the comments and we can see about getting this combo of WordPress + CloudFlare + ActivityPub working, I’ve seen so many people complain about this not working with this very popular combo of technologies in the WordPress support forums.

Maybe related content

Likes, Bookmarks, and Reposts

  • Gergely Imreh
  • mkalina
  • Andy Fragen
  • Shawn Hooper (he/him)
  • Amanda Carson
  • meagan hanes
  • Kavya :cools: ⚧️ ️‍⚧️ ☢❔
  • Ross McKay
  • Courtney Robertson
  • Tim Nolte
  • Tim Nolte

18 responses to “What’s with ActivityPub + WordPress + Cloudflare not working?”

  1. Jason Tucker

    Let me know what you think @tim @tomfinley

  2. shauny

    @jasontucker I am trying to read your post but it’s not loading for me, the page only half loads. Also what’s with the hashtags that are all hex codes? I’m so confused.

  3. Jason Tucker

    @shauny wow, that’s really weird

  4. Jason Tucker

    @shauny of course the first time I ever try to actually put code on my website, something funky like this happens

  5. Jason Tucker

    @shauny OK do you mind giving me a refresh?

  6. Dustin Rue

    @jasontucker https://blog.dustinrue.com/2022/12/wordpress-fediverse-and-caching/ . Of course, this creates an issue that your author page is not being cached
    WordPress, Fediverse and Caching – Dustin’s Blog

  7. Jason Tucker

    @dustinrue well the real issue is that it’s not returning the correct content type

  8. shauny

    @jasontucker sorry was out. It’s working now. The long URLs add horizontal scrolling on mobile though, just FYI. I tried adding my site to the Fediverse but found that it’s probably better to just link to posts from a separate account, and track replies etc using webfinger. Less hassle!

  9. Jason Tucker

    @dustinrue yep that fact that it was returning the incorrect content type is the whole crux of this blog post. Since using Cloudflare is very popular with WordPress websites I am under the impression that most of the support requests that come in from people is because of the fact that Cloudflare isn’t returning the response in the form of application/activity+json

  10. Jason Tucker

    @dustinrue yeah you can force the format by adding /activitypub to the end of the author url https://wpwatercooler.com/author/wpwatercooler/activitypub

  11. Jason Tucker

    @dustinrue yeah @tim and @tomfinley were also working out what the deal is here, Tim is using lightspeed and was having weird issues, i think this is a combo of cloudways hosting and their caching along with cloudflare for me.

  12. growplugins

    @jasontuckerYou could try the WPMU Dev plugins (Defender + Hummingbird + Smush). I haven’t used their paid CDN/WAF yet, but their lite plugins are very impressive to me.

  13. Jason Tucker

    @wpwatercooler I sent this to @boogah, I think this is my 2023 question #willitactivitypub
    willitactivitypub

  14. Steve Sawczyn

    @jasontucker I’d love to go all in with my WordPress blog as well, but that doesn’t seem to be possible if hosting with WordPress.com. Why they haven’t added activitypub in any way over there is a mystery to me.

  15. Gergely Imreh

    @jasontucker @tim looks like I went down on a parallel path , the train of thought from the blogpost looks very similar 🙂 well, well…

  16. Jason Tucker

    @imrehg @tim yeah I’ve learned to document stuff like this so smarter people can come by and tell me I did something wrong then

  17. Gergely Imreh

    @jasontucker @tim that sounds like how wisdom works 🙂

  18. Tim Nolte

    @jasontucker @glecharles yeah, to follow up with Jason, there are known issues with both sites using Let’s Encrypt certificates as well as sites that have caching. More details into your SSL & caching setups might help to pinpoint the issue.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.