How to Fix 'BED File Not Sorted' Errors: A Troubleshooting Guide
By Dr. Zubair Khalid, DVM, MS, PhD ·

The most likely cause of a "BED file not sorted" error is that the file was sorted lexicographically instead of numerically, so chr10 lands before chr2 and chr2:1000 lands before chr2:900. The first thing to check is whether your file passes a coordinate sort test. Run sort -C -t$'\t' -k1,1 -k2,2n yourfile.bed and read the exit status. A silent success means the file is coordinate sorted. A non-zero exit code means the file is not, and the rest of this guide tells you why and what to do about it.
Interval tools fail loudly on unsorted input because they stream through two files in lockstep. A single out-of-order record breaks the sweep, and the tool has no safe way to continue. The error is almost always a data preparation problem rather than a software bug, and it is almost always fixable in one or two commands once you know which of about ten causes you are dealing with.
What "Sorted" Means for a BED File
BED stands for Browser Extensible Data. It is a plain-text, tab-delimited format where each line describes one genomic interval. The minimum form has three columns: chromosome name, start, end. Coordinates are 0-based and half-open, meaning the start position is included in the interval and the end position is not. A BED line chr1 100 200 covers bases 101 through 200 in 1-based terms, or positions 100 through 199 in 0-based terms. This is defined by the UCSC Genome Browser specification, which is the reference standard for the format.
BED is one of the four dominant plain-text formats in genomics, alongside FASTA, FASTQ and VCF, and all four are large enough that the choice of compressor matters in production pipelines [1]. The four formats have different internal structure. FASTA is a header line followed by sequence. FASTQ carries the same sequence plus a per-base quality string, which is why a format-aware compressor has to treat the two differently to get good ratios [1]. BED carries no sequence at all, only coordinates and optional annotation columns.
Sorting a BED file means satisfying two separate conditions:
- Within each chromosome, the start coordinate never decreases from one line to the next.
- Chromosomes appear in some consistent order across the whole file, with each chromosome occupying one contiguous block.
Some tools check only condition 1. Others check both. And a third group, including bedtools intersect -sorted, checks that two or more files share the same chromosome order, which is a stricter requirement than either file being sorted on its own.
That third case is where most confusing failures come from. Each file is internally sorted and each would pass a solo check, but the pair fails because one file lists chr1, chr2, chr10 and the other lists chr1, chr10, chr2.
Why tools demand sorted input
The sorted requirement exists for performance. When two interval files are sorted in the same order, bedtools intersect can walk both files once, comparing only the intervals that currently overlap in coordinate space. That is a linear-time sweep. When the input is unsorted, the tool has to load one file into memory or build an index, which costs RAM and time on files that routinely reach millions of intervals.
The same logic applies across the ecosystem. A protocol for extracting targeted regions from a multi-sample VCF takes a reference genome, a BED file of target regions and a sample list, and pulls the targeted genotypes into a new VCF for population-genetic analysis [2]. A consensus variant-calling pipeline uses a BED file to apply coverage-aware voting so that low-coverage sites do not dominate the consensus call [3]. In both cases the BED file is a set of regions that the engine must traverse alongside another file. Sorted input is what makes that traversal cheap.
BED files are also frequently produced by generators rather than by hand. BED-Craft builds a BED file directly from a list of gene names for nanopore adaptive sampling, adding strand-aware buffer regions upstream and downstream of each target [4]. Any generator that writes records in the order of its input inherits that input's ordering, and a list of gene symbols is not a coordinate-sorted list. This is one of the quiet ways unsorted BED files enter a pipeline.
The Most Common Cause: Lexicographic Instead of Numeric Sort
Plain sort compares text. It does not know that the second column is a number. Given these four lines:
chr1 1000 2000
chr2 900 1000
chr2 1000 1100
chr10 500 900
sort file.bed produces chr1, then chr10, then chr2, then chr2. Inside chr2 it places 1000 before 900, because the character 1 sorts before 9. Both results violate the BED sort contract. chr10 before chr2 breaks chromosome blocking, and 1000 before 900 breaks the non-decreasing start rule.
The fix is to tell sort which field to compare and how:
sort -t$'\t' -k1,1 -k2,2n input.bed > output.bed
The flags break down as follows.
-t$'\t'sets the field separator to a literal tab. Without it,sortuses runs of blanks, which can merge fields on malformed lines.-k1,1sorts on field 1 only, up to field 1. This keeps chromosomes grouped.-k2,2nsorts on field 2 as a number.
The n modifier is the critical part. Drop it and you are back to lexicographic ordering on the start column.
This command produces chr1, chr10, chr2 for the chromosome blocks. That satisfies condition 1 (each block is contiguous) and condition 2 (the order is consistent), so most tools accept it. It does not produce the canonical chr1, chr2, chr10 order that a reference genome dictionary expects. See the section on reference-order sorting below.
Locale effects
GNU sort respects the locale. In some non-C locales, character collation rules can change how punctuation and case are ordered in chromosome names such as chr1_KI270706v1_random. For reproducible output across machines, prefix the command:
LC_ALL=C sort -t$'\t' -k1,1 -k2,2n input.bed > output.bed
LC_ALL=C forces byte-order collation, which is what most genomics tools assume internally.
Troubleshooting Table
| Symptom | Likely cause | How to confirm | Fix | ||
|---|---|---|---|---|---|
bedtools reports an out-of-order record that looks fine | Lexicographic sort, chr10 before chr2 or 1000 before 900 | sort -C -t$'\t' -k1,1 -k2,2n file.bed fails | Re-sort with -k2,2n | ||
Each file passes a solo sort check but -sorted still fails | The two files use different chromosome orders | `cut -f1 a.bed \ | uniq and compare to cut -f1 b.bed \ | uniq` | Sort both with the same -faidx or -g file |
| Error after editing the file in a spreadsheet | CRLF line endings or space-separated columns | `cat -A file.bed \ | head shows ^M` or spaces | tr -d '\r' < in.bed > out.bed, then re-sort | |
tabix refuses to index | File is sorted but not bgzip-compressed, or not sorted at all | file file.bed.gz and `zcat file.bed.gz \ | head` | sort then bgzip, then tabix -p bed | |
| GATK or Picard rejects the interval list | Chromosome order differs from the sequence dictionary | Compare chromosome names against the .dict file | Re-sort using the dictionary order | ||
| Parser stops at line 1 | track, browser or # header lines | `grep -n -E '^(track\ | browser\ | #)' file.bed` | Remove headers for tools that do not accept them |
bedtools merge complains about the same interval twice | Duplicate records, not a sort failure | `sort file.bed \ | uniq -d \ | head` | Deduplicate with bedtools merge after sorting |
| Interval has start greater than or equal to end | Malformed coordinates, often from a 1-based source | `awk -F'\t' '$2 >= $3' file.bed \ | head` | Fix the offending records at the source | |
| File is now empty after you "sorted" it | Shell redirection truncated the input before sort read it | ls -l file.bed shows 0 bytes | Restore from backup, never redirect onto the input | ||
| Works locally, fails on the cluster | Different sort build, locale or GNU versus BSD sort | sort, version on both machines | Pin LC_ALL=C and use bedtools sort |
Causes in Order of Frequency
1. Lexicographic sort
Covered above. This is the single most common cause and it accounts for most reports of a "BED file not sorted" error. Spreadsheet applications sort text columns alphabetically by default, so a BED file exported from Excel or a similar tool is nearly always lexicographically sorted and nearly always wrong.
2. Chromosome naming mismatch
A file sorted on 1, 2, 3 and a file sorted on chr1, chr2, chr3 are each internally valid, but they do not share an order. bedtools intersect -sorted fails when it reaches the first record whose chromosome it cannot match in the expected order.
Confirm the naming scheme with:
cut -f1 file.bed | sort -u | head -30
If one file shows 1 and another shows chr1, you have found the problem. The fix is to strip or add the prefix consistently:
sed 's/^chr//' file.bed > file.noprefix.bed
Then re-sort both files by the same rule. Decide on one convention before you start the project, not after.
3. Header and comment lines
The BED format allows track and browser lines at the top for display purposes. Many command-line tools skip them. Some do not. A track line that reaches a parser as a data record has no numeric start column and can trigger a sort error or a parse error that reads like one.
Check with:
grep -n -E '^(track|browser|#)' file.bed
If headers exist and the downstream tool rejects them, remove them for that step:
grep -v -E '^(track|browser|#)' file.bed > clean.bed
Keep the original with headers for display in a genome browser, and use the cleaned version for computation.
4. Reference-order mismatch
This is the subtle one. A BED file sorted as chr1, chr10, chr2, ..., chr22, chr3, ... is internally consistent and passes sort -C. A GATK interval list, a Picard tool or a bedtools sort -faidx run all expect the order defined by the reference sequence dictionary or the .fai index, which is typically chr1, chr2, ..., chr10, ..., chr22, chrX, chrY, chrM.
Build the index if you have not:
samtools faidx reference.fa
The resulting reference.fa.fai has two useful columns: chromosome name and length. Sort against it with:
bedtools sort -i input.bed -faidx reference.fa.fai > sorted.bed
Or pass a genome file of two columns (name and size) with -g:
cut -f1,2 reference.fa.fai > genome.txt
bedtools sort -i input.bed -g genome.txt > sorted.bed
bedtools sort -faidx respects the order of the index file, which is the order samtools faidx wrote when it scanned the FASTA. That is the same order the rest of the reference-based toolchain expects.
5. Malformed coordinates
A line with start greater than or equal to end cannot be sorted meaningfully. Some parsers report it as a sort error because the interval engine cannot place it. Find them:
awk -F'\t' 'BEGIN{OFS="\t"} NF>=3 && ($2+0) >= ($3+0)' input.bed | head
Zero-length intervals (start equals end) are legal in some contexts and rejected in others. Check the tool's documentation before deleting them.
A related and very common error is treating a 1-based coordinate system as 0-based. VCF POS is 1-based. BED start is 0-based. Converting VCF to BED by copying POS into the start column shifts every interval by one base and, in some encodings, produces off-by-one errors that can look like ordering problems when adjacent intervals collide.
6. Space-separated instead of tab-separated
BED is defined as tab-delimited. Files that use runs of spaces instead of tabs sometimes parse and sometimes do not, depending on the tool. sort -t$'\t' then treats the whole line as one field and does not sort correctly.
Detect with:
cat -A input.bed | head -3
Tabs appear as ^I in that output. If you see plain spaces between columns, convert:
awk 'BEGIN{FS="[ ]+"; OFS="\t"} {print $1,$2,$3}' input.bed > fixed.bed
7. CRLF line endings
Files that passed through Windows produce lines ending in \r\n. The \r becomes part of the last field. In a three-column BED this usually breaks parsing rather than sorting, but in multi-column BED it can corrupt the end coordinate and produce records that compare incorrectly.
Detect with:
cat -A input.bed | head -1
A trailing ^M confirms it. Strip it:
tr -d '\r' < input.bed > fixed.bed
Then re-sort.
8. Compression and indexing
sort cannot read gzip in most implementations. Sorting a .bed.gz directly usually fails or produces garbage. Decompress, sort, then recompress with bgzip, which produces the block-gzip format that tabix requires:
zcat input.bed.gz | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n | bgzip -c > sorted.bed.gz
tabix -p bed sorted.bed.gz
tabix builds a .tbi index and requires both bgzip compression and coordinate sorting in the same order used for the index. If the sort order and the index order disagree, queries return wrong or missing records without an error.
9. Concatenating sorted files
Two correctly sorted BED files concatenated do not make a correctly sorted BED file unless the second file's entire coordinate range comes after the first file's. If file A ends at chr5 and file B starts at chr1, the concatenation is out of order. Always re-sort after cat:
cat a.bed b.bed | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n > merged.bed
10. Duplicates and overlaps
Duplicate records do not make a file unsorted, but they do make bedtools merge fail with a message that some users read as a sort error. Merge requires sorted input and typically expects to combine overlapping or adjacent intervals. Deduplicate first if exact duplicates are not meaningful:
LC_ALL=C sort -t$'\t' -k1,1 -k2,2n -u input.bed > dedup.bed
Note that -u on the full line removes exact duplicate lines only. Two intervals with the same coordinates but different names are not exact duplicates.
Diagnostic Sequence
Work through these steps in order. Each one is cheap and rules out a large class of causes.
- Check line count before and after every operation.
wc -l input.bed output.bed. If the output is empty or shorter than expected, you truncated the input and the problem is not sorting at all. - Check for headers.
head -5 input.bed. Look fortrack,browseror#. - Check column separation.
cat -A input.bed | head -3. Look for^I(tab) versus spaces and for trailing^M. - Check the chromosome order.
cut -f1 input.bed | uniq | head -30. This shows the order of blocks as they appear in the file. - Run the sort check.
sort -C -t$'\t' -k1,1 -k2,2n input.bed. Exit code 0 means coordinate sorted, 1 means not. - Run a natural-order check.
sort -C -t$'\t' -k1,1V -k2,2n input.bed. This tests whether chromosomes are inchr1, chr2, chr10order rather thanchr1, chr10, chr2order. - Compare to the reference. Diff the chromosome block list against
cut -f1 reference.fa.fai. - Re-sort against the reference order if steps 5 and 6 pass but the downstream tool still fails. The tool is asking for dictionary order, not generic sort order.
- Re-run the failing command on a 1,000-line subset. If the subset works, the problem is in a specific region of the file and you can bisect to find the offending record.
Fixes and Exact Commands
Canonical coordinate sort
LC_ALL=C sort -t$'\t' -k1,1 -k2,2n input.bed > sorted.bed
Use this when the downstream tool only needs monotonic starts within contiguous chromosome blocks and does not care about the chromosome block order.
Natural chromosome order
LC_ALL=C sort -t$'\t' -k1,1V -k2,2n input.bed > sorted.bed
-V is a version-sort modifier that treats digit runs as numbers, giving chr1, chr2, chr10. It handles chr1 through chr22 correctly and handles chrX, chrY and chrM only by accident of alphabetical position. It does not reproduce reference dictionary order, which typically places chrX and chrY after chr22 and puts chrM last.
Reference dictionary order
samtools faidx reference.fa
bedtools sort -i input.bed -faidx reference.fa.fai > sorted.bed
This is the correct choice for GATK, Picard and any pipeline whose steps all share one reference.
Sorting multiple files identically
When two files must be compared, sort both with the same reference file:
bedtools sort -i a.bed -faidx reference.fa.fai > a.sorted.bed
bedtools sort -i b.bed -faidx reference.fa.fai > b.sorted.bed
bedtools intersect -a a.sorted.bed -b b.sorted.bed -sorted > overlap.bed
Sorting only one file is a frequent mistake. -sorted is a promise about both inputs.
Multi-column BED
sort -k1,1 -k2,2n preserves all columns because sort reorders whole lines. Nothing is lost. bedtools sort also preserves all columns. You do not need to cut down to three columns to sort and then paste columns back.
Sorting with awk when sort is unavailable
Rare, but useful in constrained environments:
awk -F'\t' '{print $1"\t"$2"\t"$0}' input.bed | LC_ALL=C sort -t$'\t' -k1,1 -k2,2n | cut -f3-
This duplicates the first two columns, sorts on the copies, then strips them. It is the same trick many legacy scripts use.
Safety note on redirection
Never write a sorted file back onto its own source. sort input.bed > input.bed truncates input.bed before sort reads it, and the result is an empty file. The same hazard applies to bedtools sort -i input.bed > input.bed and to zcat input.bed.gz | sort > input.bed.gz. Work on a copy, write to a new name, and only move the new file over the old one after checking wc -l.
Verifying the Fix
Sorting commands can succeed and still produce the wrong order. Verify before you move on.
sort -C -t$'\t' -k1,1 -k2,2n sorted.bedreturns 0.cut -f1 sorted.bed | uniq | head -30shows each chromosome exactly once in a block.cut -f1 sorted.bed | uniq | wc -lmatches the number of distinct chromosomes in your reference.awk -F'\t' 'prev==$1 && $2<p {print NR": "$0; exit} {p=$2; prev=$1}' sorted.bedprints nothing if starts are monotonic within each block.bedtools intersect -a a.sorted.bed -b b.sorted.bed -sorted > /dev/nullexits without an error, which is the practical test that matters.
For a BED file headed for tabix, confirm the compression type as well:
file sorted.bed.gz
The output should name BGZF or gzip in a way that indicates block-gzip. Plain gzip from gzip -c will not index correctly.
Prevention
Fix the order at the source
The most reliable prevention is to make every BED file in a project sorted against the same reference, using the same command, at the moment it is written. Add one sorting step immediately after every BED-producing step in your workflow. Sorting a few thousand intervals is nearly free. Debugging a failed multi-hour pipeline because of a header line is not.
Standardize chromosome names in the reference
Choose chr-prefixed or unprefixed names and enforce it. Most human references are distributed with chr prefixes, but some annotation sets are not. Converting once, early, and documenting the choice prevents a whole category of downstream comparison failures.
Avoid spreadsheets for BED files
Spreadsheet applications reorder columns, convert numeric-looking fields, strip leading zeros, change line endings and add quoting. Any one of these breaks a BED file. If a collaborator sends you a spreadsheet, export to tab-delimited text and run the diagnostic sequence before using it.
Keep a genome file in the project
Ship a genome.txt with two columns (chromosome, length) alongside the reference. Every sorting step can then use -g genome.txt and there is no ambiguity about order. Regenerate it whenever the reference changes.
Validate on ingest
Add a validation step at the start of every pipeline run:
set -euo pipefail
sort -C -t$'\t' -k1,1 -k2,2n "$BED" || { echo "BED is not coordinate sorted"; exit 1; }
Fail early with a clear message instead of failing deep inside an interval engine with a message about a single record.
Version your intermediates
Compressed intermediates save space and cost visibility. A .bed.gz that was sorted under one reference and reused under another is a silent source of wrong results. Store intermediates uncompressed during development and compress only at the archive step.
Common Mistakes and Limitations
Assuming bedtools sort matches the reference order. bedtools sort without -faidx or -g groups chromosomes in its own default order. That order is consistent, which satisfies most tools, but it is not necessarily the dictionary order that GATK expects. If a downstream step uses a .dict file, sort with the .fai from the same reference.
Sorting one file of a pair. -sorted in bedtools intersect is a claim about every input. Sorting -a and forgetting -b fails at the point where the two sweeps diverge.
Treating -V as universal. Version sort gets chr1, chr2, chr10 right but has no concept of chrX or chrM placement. Use -faidx when the reference matters.
Sorting after compression. sort on a .gz file either fails or sorts byte-order compressed data, which is meaningless. Decompress, sort, compress.
Confusing BED coordinates with VCF coordinates. BED is 0-based half-open. VCF is 1-based. A BED file built by copying VCF POS into column 2 is offset by one base, and a BED file built by copying POS into column 2 and using the same value for column 3 produces zero-length intervals.
Forgetting BED12 block structure. A BED12 record has a thick start, thick end, item RGB, block count, block sizes and block starts. A record can pass a simple chrom/start/end sort check while its internal block coordinates are inconsistent. Sorting does not repair internal block structure, and no sort command will detect a bad block list.
Expecting one canonical order. There is no single universal BED sort order. The format specification defines column meaning, not chromosome ordering. Order is a convention imposed by each tool and each reference. That flexibility is why the error appears in so many different wordings across tools.
Ignoring locale. A pipeline that works on your laptop and fails on a compute node is often a locale difference in sort. Pin LC_ALL=C.
Limitations. These fixes assume a well-formed BED file with numeric start and end columns. Files with embedded quoted fields, escaped tabs or non-ASCII chromosome names need manual inspection. The exact chromosome ordering required by a specific tool should always be confirmed against that tool's documentation and the reference sequence dictionary in use, because ordering conventions are tool-specific and can change between versions.
Frequently Asked Questions
Why does bedtools say my BED file is not sorted when it looks sorted to me?
Because the human eye reads chr10 as coming after chr2 and the software reads it as coming before. Plain text sorting puts chr10 before chr2 and puts 1000 before 900. Re-sort with a numeric key on column 2 and confirm with sort -C -t$'\t' -k1,1 -k2,2n file.bed.
Does sort -k1,1 -k2,2n fix every BED sorting error?
No. It fixes coordinate ordering within contiguous chromosome blocks, which resolves the majority of cases. It does not reproduce a reference dictionary order, does not remove header lines, does not fix CRLF endings or space-separated columns, and does not help when two files use different chromosome naming schemes.
Do I need to sort both files for bedtools intersect -sorted?
Yes. The -sorted flag asserts that both inputs are sorted in a compatible order. Sorting only the -a file is a common cause of a late failure. Sort both with the same genome file or the same .fai index.
How do I sort a BED file to match a reference genome order?
Create or reuse a .fai index with samtools faidx reference.fa, then run bedtools sort -i input.bed -faidx reference.fa.fai. Alternatively, pass a two-column genome file of chromosome names and lengths to bedtools sort -g genome.txt. Both reproduce the dictionary order used by GATK, Picard and similar tools.
Can I sort a gzipped BED file directly?
Not with standard sort, which reads plain text. Decompress, sort, and recompress with bgzip, which produces the block-gzip format that tabix can index. Plain gzip output will not produce a working .tbi index.
Why does my BED file fail after I edit it in a spreadsheet?
Spreadsheet applications frequently change line endings to CRLF, replace tabs with spaces, reinterpret numeric fields, and reorder columns. Check with cat -A file.bed | head and look for ^M and for spaces where tabs should be. Strip the carriage returns with tr -d '\r' and re-sort.
What is the difference between BED sort order and VCF sort order?
VCF requires records sorted by chromosome and by 1-based position, with contigs typically in reference dictionary order. BED requires 0-based half-open intervals sorted by chromosome and by 0-based start. The two formats use different coordinate origins, so a BED file derived from a VCF needs an explicit coordinate conversion as well as a sort.
Is a BED file 0-based or 1-based?
BED is 0-based and half-open according to the UCSC specification. The start coordinate is the first base of the interval and the end coordinate is one past the last base. This differs from GFF, VCF and most other genomics formats, which are 1-based, and the mismatch is a regular source of off-by-one errors when converting between formats.
Further Reading
- Ultrasonic Evaluation of Laser Scanning Speed Effect on the Spectral Properties of Three-Dimensional-Printed Metal Phononic Crystal Artifacts.
- Prospective associations between 24-h device-measured occupational and leisure-time physical activity and register-based musculoskeletal-related primary healthcare utilization among Danish workers.
Sources
- Byte-exact, format-aware compression for FASTA, FASTQ, BED and VCF files.
- A Protocol to Extract a Specific Genomic Region from a Public Whole-Genome Database and Modify Analytical Bin Length for Population Genetic Studies.
- ChoCallate: a computational pipeline for consensus germline variant calling in genomic sequencing data.
- BED-Craft for nanopore adaptive sampling: a tool for generating bed files with gene names as input data for enrichment sequencing.