Python Tic Tac Toe Game
My strongest objection is to your use of recursion as a kind of goto:


if input("Play again (y/n)\n") == "y":
    print()
    tic_tac_toe()



I suggest changing the tic_tac_toe() function to play just one game, then calling it in a loop.

You do some similar recursive calls in p1() and p2().

Board layout

Your board layout is


1 2 3
4 5 6
7 8 9



For a better user experience, I suggest flipping it upside-down to mimic a computer number keypad.

I think that the code would be less confusing if the board labels matched the list indices.

Handling two players

The p1() function resembles p2(), and the main loop contains two copies of code that is mostly the same.  check_board() also has some repetition.  The root cause is the need to associate Player 1 with the symbol X and the verbal description "cross", and Player 2 with the symbol O and the verbal description "nought".  The code would be easier to generalize if you just called them "Player X" and "Player O", and I think that the user experience would be enhanced as well.  (You could also generalize the code without such a simplification by using, say, a namedtuple to represent each player.)

p1() and p2() could be simplified by moving the validation into choose_number(), especially if you didn't care to distinguish between the "You can't go there" and "That's not on the board" error messages.

Implementation details


In choose_number(), there is no need for a nested while True.  You also don't need to write continue.
Renaming check_board() to is_game_over() would make its purpose more obvious
win_commbinations contains a misspelling, and would be better as WIN_COMBINATIONS to indicate that it is a constant.  board[a[0]] == board[a[1]] == board[a[2]] is ugly, and would be better expressed as

for a, b, c in WIN_COMBINATIONS:
    if board[a] == board[b] == board[c] …

In check_board(), you used the variable a for two different purposes, which is confusing.  I would just rewrite the second half of the function entirely as

if 9 == sum((pos == 'X' or pos == 'O') for pos in board):
    print("The game ends in a tie\n")
    return True

There is no need for the end variable.  (In general, such flag variables should be eliminated.)


Suggested solution

def tic_tac_toe():
    board = [None] + list(range(1, 10))
    WIN_COMBINATIONS = [
       (1, 2, 3),
       (4, 5, 6),
       (7, 8, 9),
       (1, 4, 7),
       (2, 5, 8),
       (3, 6, 9),
       (1, 5, 9),
       (3, 5, 7),
    ]

    def draw():
        print(board[7], board[8], board[9])
        print(board[4], board[5], board[6])
        print(board[1], board[2], board[3])
        print()

    def choose_number():
        while True:
            try:
                a = int(input())
                if a in board:
                    return a
                else:
                    print("\nInvalid move. Try again")
            except ValueError:
               print("\nThat's not a number. Try again")

    def is_game_over():
        for a, b, c in WIN_COMBINATIONS:
            if board[a] == board[b] == board[c]:
                print("Player {0} wins!\n".format(board[a]))
                print("Congratulations!\n")
                return True
        if 9 == sum((pos == 'X' or pos == 'O') for pos in board):
            print("The game ends in a tie\n")
            return True

    for player in 'XO' * 9:
        draw()
        if is_game_over():
            break
        print("Player {0} pick your move".format(player))
        board[choose_number()] = player
        print()

while True:
    tic_tac_toe()
    if input("Play again (y/n)\n") != "y":
        break