Why the pre_get_posts hook can cause apache stopped working?

As per this WPSE thread you are actually putting overload to the apache. That’s why it’s reacting like that. 🙂

So let’s cut the overload, use is_main_query() properly, and just add a simple conditional to your pre_get_posts hooked function:

function e_exclude_posts( $query ) {
   if( !is_admin() && $query->is_main_query() && $query->is_tax('my_tax') ) {
       $expired = g_get_all_the_expired_posts(); //now this won't create a problem
       $query->set( 'post__not_in', $expired );
   }
}
add_action( 'pre_get_posts', 'e_exclude_posts' );

WHY?

The Codex says:

This function is most commonly used within hooks to distinguish WordPress’ main query (for a page, post, or archive) from a custom/secondary query.

So by appending this conditional we are actually saying WordPress to stick only with the main query, and not to do things for all the queries – so we are cutting the overload and let the apache some relief. 🙂

EDIT

as Pieter Goosen suggested to use !is_admin() to do the query modification only on front-end, I edited my answer and added the conditional. Thanks Pieter.

EDIT #2

And adding a global variable is a bad idea, as even the content of that global variable is not needed, the global variable will simply load the content on every load – that surely is an overkill. So the best way doing this is to call the function within the function as shown in this answer. Thanks to @userabuser and @PieterGoosen for their expert advices.