I finally figured this out. It was just me being an idiot (no big surprise). I had configured the phone in FreePBX as an S505 when in reality, it was an S500. Everything worked as it should have except HTTPS provisioning, including HTTP provisioning with a username and password. Turns out, it matters!
This one’s on me, at least most of it. But I’ll reserve just a little responsibility for FreePBX. If I choose the wrong model, it seems to me that it shouldn’t work at all so that I quickly know that I screwed up. They say the best lie is one that contains mostly truth. Same here; the best way to miss the problem is for it to mostly work even though it shouldn’t.
It’s certainly possible that if I understood the inner workings of FreePBX better I might see how this is expected behavior. But I believe it can be better than that.
Thanks everyone for your help.
Someone from Sangoma care to comment?