pre_get_posts works in post type archive but not in single post

The next_post_link() and previous_post_link() functions do not use the same query that 'pre_get_posts' affects. They use their own custom queries. You have to use other filters to affect them.

Utimately, the get_adjacent_post() function does the bulk of the work for the next_post_link() and previous_post_link() functions.

Here is part of that function:

$adjacent = $previous ? 'previous' : 'next';
$op = $previous ? '<' : '>';
$order = $previous ? 'DESC' : 'ASC';

$join  = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_cat, $excluded_categories );
$where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare("WHERE p.post_date $op %s AND p.post_type = %s AND p.post_status="publish" $posts_in_ex_cats_sql", $current_post_date, $post->post_type), $in_same_cat, $excluded_categories );
$sort  = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1" );

To change the sort order for the next post use the 'get_next_post_sort' filter and use the 'get_previous_post_sort' filter to change the sort for the previous link.

You can see from this code snippet that the value in $order in the original query depends on which filter you are writing code for.

Sorry, I don’t know SQL well enough to write that part of the query.