answer was to use
<httpErrors existingResponse="Replace" errorMode="Custom">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/pages/404.aspx?he" responseMode="ExecuteURL" />
</httpErrors>
and not to have any system.web customErrors
this worked for both .aspx and non .aspx requests.
bizarrely this combination did not come up in any of the blog posts and stackoverflow answers I had investigated, it was just luck I tried it.