FIXED: The site was proxied through Cloudflare, and there was a http vs. https mixed source conflict. The solution was to install the plugin <SSL Insecure Content Fixer> and set it to <HTTP_X_FORWARDED_PROTO>. Now the backup codes are generated correctly and the user profile page says “10 unused backup codes remaining” as expected. So it’s all good!