wordpress custom search page pagination returns 404

You should use pre_get_post action to alter the search query. It is much better approach and you will avoid unnecesary queries (actually, with your code, WordPress already performed the database query for the current request but you discard it and perform a new database query).

add_action( 'pre_get_posts', 'custom_search_query' );
function custom_search_query( $query ) {
    // Alter only main query, in frontend and for search requests
    if( $query->is_search && ! is_admin() && $query->is_main_query() ) {

        $meta_query = array(
            'relation' => 'OR'
        );
        $meta_query[] = array(
            'key'     => 'some_key',
            'value'   => get_search_query(),
           ' compare' => 'LIKE',
        );
        $meta_query[] = array(
           'key'     => 'some_other_key',
           'value'   => get_search_query(),
           'compare' => 'LIKE',
        );

        // The 'OR' relation make that posts
        // are required to match at least one the meta queries.
        // If that is not what you want, we need some other logic
        // Maybe including a fake meta query that always verify to true?

        $query->set( 'meta_query', $meta_query );

    }
}

If you want to include any custom field, even the custom fields you don’t know about, you need to filter the join and where clausules of the main query (source):

<?php
/**
 * Extend WordPress search to include custom fields
 *
 * http://adambalee.com
 */

/**
 * Join posts and postmeta tables
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join
 */
function cf_search_join( $join ) {
    global $wpdb;

    if ( is_search() ) {    
        $join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
    }

    return $join;
}
add_filter('posts_join', 'cf_search_join' );

/**
 * Modify the search query with posts_where
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where
 */
function cf_search_where( $where ) {
    global $pagenow, $wpdb;

    if ( is_search() ) {
        $where = preg_replace(
            "/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
            "(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where );
    }

    return $where;
}
add_filter( 'posts_where', 'cf_search_where' );

/**
 * Prevent duplicates
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_distinct
 */
function cf_search_distinct( $where ) {
    global $wpdb;

    if ( is_search() ) {
        return "DISTINCT";
    }

    return $where;
}
add_filter( 'posts_distinct', 'cf_search_distinct' );
?>

Now you can use standard template hierarchy, you don’t need custom pagination stuff and avoid unnecessary database queries.