Assuming there is exactly one level of hierarchy possible like your example suggests:
SELECT child.*
FROM   tbl      AS child
LEFT   JOIN tbl AS parent ON parent.id = child.parent_id
ORDER  BY COALESCE(parent.id, child.id)  -- order by parent if exists
        , parent.id IS NOT NULL          -- parent first per group
        , child.id;                      -- order rest by id
The join is only needed if we sort by some additional attribute like a "name" (which is the typical case as values of surrogate IDs have no meaning). While only sorting by ID like you demonstrate, we don't need the join as all information is already there (like also demonstrated by Gordon). Then we can simplify:
SELECT *
FROM   tbl
ORDER  BY CASE WHEN parent_id = 0 THEN id ELSE parent_id END
        , parent_id <> 0
        , id;
- The 2nd ORDER BYitem is needed to sort parent before its children. Works becauseFALSEsorts beforeTRUE. See:
- The last ORDER BYitem is only required if there can be multiple children.
db<>fiddle here - with extended test case to demonstrate relevance of ORDER BY items.